├── src ├── locales │ ├── en │ │ └── messages.js │ └── zh │ │ └── messages.js ├── img │ ├── asset.png │ ├── avatar.png │ ├── guides.jpg │ ├── guides.png │ ├── metamask.png │ ├── turnon1.png │ ├── turnon2.png │ ├── unisat.png │ ├── logo_nostr.png │ ├── banner_1@2x.png │ ├── banner_2@2x.png │ ├── banner_2_m@2x.png │ ├── banner_claim.png │ ├── icon-nostr.jpeg │ ├── rsz_ethereum.jpg │ ├── unknown-logo.png │ ├── Banner-1-mobile.png │ ├── banner_claim_m.png │ ├── coinbaseWallet.png │ ├── banner_1_mobile@2x.png │ ├── banner_claim_coin.png │ ├── swap.svg │ ├── long.svg │ ├── success.svg │ ├── flag_en.svg │ ├── ic_telegram.svg │ ├── ic_checked.svg │ ├── ic_info.svg │ ├── ic_wallet_24.svg │ ├── ic_twitter.svg │ ├── flag_zh.svg │ ├── ic_btc_40.svg │ ├── Receive.svg │ ├── addressBook.svg │ ├── Send.svg │ ├── Transfer.svg │ ├── walletconnect-circle-blue.svg │ ├── Asset.svg │ ├── logo.svg │ ├── ic_coingecko_16.svg │ ├── ic_coingecko_hover_16.svg │ └── wallet-connect-text.svg ├── config │ ├── localStorage.js │ ├── graphqlClient.js │ ├── contract.js │ ├── chains.js │ ├── constants.js │ ├── icons.js │ └── wagmiConfig.js ├── fonts │ ├── Poppins │ │ └── Poppins-Medium.ttf │ ├── OpenSans │ │ └── OpenSans-Medium.ttf │ └── svg │ │ ├── unfold.svg │ │ ├── telegram.svg │ │ ├── twitter.svg │ │ └── gitbook.svg ├── pages │ ├── Transfer │ │ └── index.jsx │ ├── Account │ │ └── comps │ │ │ ├── ProModal │ │ │ ├── index.scss │ │ │ └── index.jsx │ │ │ ├── AddressBook │ │ │ └── index.scss │ │ │ └── Transfer │ │ │ └── index.scss │ ├── Deposit │ │ ├── comps │ │ │ ├── DepositHelpModal.scss │ │ │ ├── DepositDescription.scss │ │ │ ├── Inscription.scss │ │ │ ├── DepositDescription.jsx │ │ │ ├── Inscription.jsx │ │ │ ├── DepositHelpModal.jsx │ │ │ ├── BRC20Fee.jsx │ │ │ └── DepositForm.scss │ │ ├── DepositFormWrapper.jsx │ │ ├── index.jsx │ │ └── index.scss │ ├── Marketplace │ │ └── comps │ │ │ ├── OrderDetail │ │ │ └── index.scss │ │ │ ├── Market │ │ │ └── index.scss │ │ │ └── Listing │ │ │ └── index.scss │ ├── Explore │ │ ├── comps │ │ │ ├── ExploreDetails │ │ │ │ └── index.scss │ │ │ └── TokenSlider │ │ │ │ ├── index.scss │ │ │ │ └── index.js │ │ ├── explore.scss │ │ └── index.jsx │ ├── PageNotFound │ │ ├── PageNotFound.css │ │ └── PageNotFound.js │ ├── Testnet │ │ ├── comps │ │ │ ├── NotConnectContainer.jsx │ │ │ ├── ClaimDescription.jsx │ │ │ ├── TokenTip.jsx │ │ │ ├── QuestList.jsx │ │ │ └── UserPointerInfo.jsx │ │ └── PioneerPoints.jsx │ └── Withdraw │ │ ├── index.jsx │ │ ├── index.scss │ │ └── comps │ │ └── WithdrawForm.scss ├── components │ ├── Common │ │ ├── ConnectWallet.scss │ │ ├── ConnectWalletButton.jsx │ │ ├── UniftTabs │ │ │ ├── index.jsx │ │ │ └── index.scss │ │ ├── ConnectWalletButton.scss │ │ ├── Modal │ │ │ ├── Modal.jsx │ │ │ └── Modal.css │ │ ├── ConnectNostr.jsx │ │ ├── SEO.jsx │ │ └── ConnectWallet.jsx │ ├── ExternalLink │ │ ├── ExternalLink.scss │ │ └── ExternalLink.jsx │ ├── Footer │ │ ├── Footer.jsx │ │ └── Footer.scss │ ├── Container │ │ ├── index.jsx │ │ └── index.scss │ ├── OutLinks │ │ ├── index.scss │ │ └── index.jsx │ ├── TextLoading │ │ └── index.jsx │ ├── Header │ │ ├── HeaderLink.jsx │ │ ├── AppNostrHeaderUser.jsx │ │ └── AppHeaderLinks.jsx │ ├── NetworkDropdown │ │ ├── index.scss │ │ ├── LanguageModalContent.js │ │ ├── LanguageModalContent.jsx │ │ └── index.jsx │ ├── EllipsisMiddle │ │ └── index.jsx │ ├── Modals │ │ ├── LanguageModal.jsx │ │ ├── OnlyMobileSupportModal.jsx │ │ ├── ConnectNostrModal.jsx │ │ ├── index.scss │ │ ├── SetupNetWorkModal.jsx │ │ ├── TurnOnNostrDrawer.jsx │ │ └── ConnectNostrOnTPModal.jsx │ ├── AddressDropdown │ │ └── AddressDropdown.scss │ └── RelayList │ │ └── index.scss ├── hooks │ ├── useSelectors.js │ ├── useDevice.js │ ├── useNostrDisconnect.js │ ├── unisatWallet │ │ ├── useGetFees.js │ │ └── useUnisatWalletSdk.js │ ├── useSignMessage.js │ ├── useAccountInit.js │ ├── useWebln.js │ └── useGetNostrAccount.js ├── lib │ ├── useScrollToTop.js │ ├── nostr-react │ │ ├── utils.js │ │ └── index.js │ ├── utils │ │ ├── userAgent.js │ │ └── math.js │ ├── i18n.js │ ├── url.js │ ├── downloadImage.js │ └── legacy.js ├── store │ ├── index.js │ └── reducer │ │ ├── marketReducer.js │ │ ├── modalReducer.js │ │ ├── basicReducer.js │ │ └── userReducer.js ├── service │ └── index.js ├── index.jsx ├── abis │ └── INostrSwapDeposit.json └── App │ ├── App.jsx │ └── Routes.jsx ├── public ├── favicon.ico └── index.html ├── .prettierrc.json ├── .babelrc ├── .editorconfig ├── .env ├── .eslintrc.js ├── .linguirc ├── .env.test ├── .env.development ├── .gitignore ├── jsconfig.json ├── LICENSE ├── README.md ├── config-overrides.js └── package.json /src/locales/en/messages.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable*/module.exports={messages:JSON.parse("{}")}; -------------------------------------------------------------------------------- /src/locales/zh/messages.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable*/module.exports={messages:JSON.parse("{}")}; -------------------------------------------------------------------------------- /src/img/asset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnfi-network/interface/HEAD/src/img/asset.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnfi-network/interface/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/config/localStorage.js: -------------------------------------------------------------------------------- 1 | 2 | export const LANGUAGE_LOCALSTORAGE_KEY = "LANGUAGE_KEY"; 3 | 4 | -------------------------------------------------------------------------------- /src/img/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnfi-network/interface/HEAD/src/img/avatar.png -------------------------------------------------------------------------------- /src/img/guides.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnfi-network/interface/HEAD/src/img/guides.jpg -------------------------------------------------------------------------------- /src/img/guides.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnfi-network/interface/HEAD/src/img/guides.png -------------------------------------------------------------------------------- /src/img/metamask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnfi-network/interface/HEAD/src/img/metamask.png -------------------------------------------------------------------------------- /src/img/turnon1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnfi-network/interface/HEAD/src/img/turnon1.png -------------------------------------------------------------------------------- /src/img/turnon2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnfi-network/interface/HEAD/src/img/turnon2.png -------------------------------------------------------------------------------- /src/img/unisat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnfi-network/interface/HEAD/src/img/unisat.png -------------------------------------------------------------------------------- /src/img/logo_nostr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnfi-network/interface/HEAD/src/img/logo_nostr.png -------------------------------------------------------------------------------- /src/img/banner_1@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnfi-network/interface/HEAD/src/img/banner_1@2x.png -------------------------------------------------------------------------------- /src/img/banner_2@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnfi-network/interface/HEAD/src/img/banner_2@2x.png -------------------------------------------------------------------------------- /src/img/banner_2_m@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnfi-network/interface/HEAD/src/img/banner_2_m@2x.png -------------------------------------------------------------------------------- /src/img/banner_claim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnfi-network/interface/HEAD/src/img/banner_claim.png -------------------------------------------------------------------------------- /src/img/icon-nostr.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnfi-network/interface/HEAD/src/img/icon-nostr.jpeg -------------------------------------------------------------------------------- /src/img/rsz_ethereum.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnfi-network/interface/HEAD/src/img/rsz_ethereum.jpg -------------------------------------------------------------------------------- /src/img/unknown-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnfi-network/interface/HEAD/src/img/unknown-logo.png -------------------------------------------------------------------------------- /src/img/Banner-1-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnfi-network/interface/HEAD/src/img/Banner-1-mobile.png -------------------------------------------------------------------------------- /src/img/banner_claim_m.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnfi-network/interface/HEAD/src/img/banner_claim_m.png -------------------------------------------------------------------------------- /src/img/coinbaseWallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnfi-network/interface/HEAD/src/img/coinbaseWallet.png -------------------------------------------------------------------------------- /src/img/banner_1_mobile@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnfi-network/interface/HEAD/src/img/banner_1_mobile@2x.png -------------------------------------------------------------------------------- /src/img/banner_claim_coin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnfi-network/interface/HEAD/src/img/banner_claim_coin.png -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "none", 3 | "semi": true, 4 | "singleQuote": false, 5 | "printWidth": 120 6 | } -------------------------------------------------------------------------------- /src/fonts/Poppins/Poppins-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnfi-network/interface/HEAD/src/fonts/Poppins/Poppins-Medium.ttf -------------------------------------------------------------------------------- /src/fonts/OpenSans/OpenSans-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnfi-network/interface/HEAD/src/fonts/OpenSans/OpenSans-Medium.ttf -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "@babel/plugin-proposal-optional-chaining", 4 | "@babel/plugin-syntax-dynamic-import" 5 | ] 6 | 7 | } -------------------------------------------------------------------------------- /src/pages/Transfer/index.jsx: -------------------------------------------------------------------------------- 1 | export default function Transfer() { 2 | return ( 3 | <> 4 |
transfer
5 | 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /src/components/Common/ConnectWallet.scss: -------------------------------------------------------------------------------- 1 | .connect-wallet-common{ 2 | // padding: 20px; 3 | font-size: 14px; 4 | text-align: center; 5 | .connect-wallet-text{ 6 | margin-bottom: 20px; 7 | } 8 | } -------------------------------------------------------------------------------- /src/pages/Account/comps/ProModal/index.scss: -------------------------------------------------------------------------------- 1 | .nostr-modal { 2 | padding-top: 20px; 3 | 4 | &-description { 5 | text-align: left; 6 | &-light { 7 | color: var(--color-text-light); 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/components/ExternalLink/ExternalLink.scss: -------------------------------------------------------------------------------- 1 | .link-underline { 2 | text-decoration: underline; 3 | cursor: pointer; 4 | display: inline-flex; 5 | color: rgba(255, 255, 255, 0.7); 6 | &:hover { 7 | color: rgba(255, 255, 255); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/config/graphqlClient.js: -------------------------------------------------------------------------------- 1 | import { Client, cacheExchange, fetchExchange } from "urql"; 2 | export const client = Client({ 3 | url: process.env.REACT_APP_API_GraphQL_URL, 4 | exchanges: [cacheExchange, fetchExchange], 5 | requestPolicy: "network-only" 6 | }); 7 | -------------------------------------------------------------------------------- /src/config/contract.js: -------------------------------------------------------------------------------- 1 | import { GOERLI } from "./chains"; 2 | const CONTRACT_CONFIG = { 3 | [GOERLI]: { 4 | currency: "0xe5dBc361575791FF107189968d4C82A1B42c1105", 5 | proxy: "0x02E8Df644226b5CE410e0696F086a08AA3F4bF87" 6 | } 7 | }; 8 | export default CONTRACT_CONFIG; 9 | -------------------------------------------------------------------------------- /src/img/swap.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/img/long.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/Footer/Footer.jsx: -------------------------------------------------------------------------------- 1 | import "./Footer.scss"; 2 | import RelayList from "../RelayList" 3 | 4 | export default function Footer({ setRelayUrls }) { 5 | return ( 6 |
7 | 8 |
9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | end_of_line = lf 4 | indent_size = 2 5 | indent_style = space 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | 9 | [*.md] 10 | max_line_length = 0 11 | trim_trailing_whitespace = false 12 | 13 | [COMMIT_EDITMSG] 14 | max_line_length = 0 15 | -------------------------------------------------------------------------------- /src/hooks/useSelectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from "reselect"; 2 | export const selectorGetSimpleTokens = createSelector(({ market }) => market.tokenList, (tokenList) => { 3 | return { 4 | tokens: tokenList.map(token => ({ 5 | tokenName: token.name, 6 | id: token.id 7 | })) 8 | } 9 | }) -------------------------------------------------------------------------------- /src/components/Container/index.jsx: -------------------------------------------------------------------------------- 1 | import "./index.scss"; 2 | export default function Container({ pageTitle = "", children }) { 3 | return ( 4 |
5 |
{pageTitle}
6 | {children} 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /src/pages/Deposit/comps/DepositHelpModal.scss: -------------------------------------------------------------------------------- 1 | .deposit-help-modal-description { 2 | margin-top:20px; 3 | &__title { 4 | color:var(--color-green-light); 5 | font-size:14px; 6 | padding-left:20px; 7 | } 8 | } 9 | .deposit-help-modal-btn { 10 | text-align: center; 11 | padding-top:10px; 12 | } -------------------------------------------------------------------------------- /src/components/OutLinks/index.scss: -------------------------------------------------------------------------------- 1 | .outLinks{ 2 | display: flex; 3 | align-items: center; 4 | justify-content: space-between; 5 | .svg-icon{ 6 | padding: 5px; 7 | background: #fff; 8 | border-radius: 50px; 9 | color: #000; 10 | } 11 | a+a{ 12 | margin-left: 10px; 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/config/chains.js: -------------------------------------------------------------------------------- 1 | export const MAINNET = 56; 2 | export const TESTNET = 97; 3 | export const GOERLI = 5; 4 | export const ETH_MAINNET = 1; 5 | export const STACKS = 10; 6 | export const AVALANCHE = 43114; 7 | export const AVALANCHE_FUJI = 43113; 8 | export const ARBITRUM = 42161; 9 | export const ARBITRUM_TESTNET = 421611; 10 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | CI=false 2 | GENERATE_SOURCEMAP=false 3 | REACT_APP_NOSTR_TOKEN_SEND_TO=npub1j86yct7kp5zrn2mq8jjsrxt0ak69kqr8u9gywpux3d0plr52dxusl0wf2t 4 | REACT_APP_API_GraphQL_URL=https://dev-nostrgraph.unift.xyz/api/graph_data 5 | REACT_APP_USDT_CONTRACT_ADDR=0xfad6367E97217cC51b4cd838Cc086831f81d38C2 6 | REACT_APP_CURRENT_ENV=prod 7 | PORT=3011 8 | -------------------------------------------------------------------------------- /src/components/TextLoading/index.jsx: -------------------------------------------------------------------------------- 1 | import { LoadingOutlined } from '@ant-design/icons'; 2 | import { Spin } from 'antd'; 3 | const antIcon = ( 4 | 10 | ); 11 | const TextLoading = () => ; 12 | export default TextLoading; -------------------------------------------------------------------------------- /src/fonts/svg/unfold.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/lib/useScrollToTop.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useHistory } from "react-router-dom"; 3 | 4 | export default function useScrollToTop() { 5 | const history = useHistory(); 6 | useEffect(() => { 7 | const unlisten = history.listen(() => { 8 | window.scrollTo(0, 0); 9 | }); 10 | return () => { 11 | unlisten(); 12 | }; 13 | }, [history]); 14 | } 15 | -------------------------------------------------------------------------------- /src/components/Container/index.scss: -------------------------------------------------------------------------------- 1 | .nostr-container { 2 | width: 100%; 3 | 4 | padding: 20px; 5 | margin: 0 auto; 6 | margin-bottom: 20px; 7 | .nostr-page-title { 8 | font-size: 22px; 9 | text-align: center; 10 | font-weight: bold; 11 | line-height: 80px; 12 | color: #fff; 13 | } 14 | } 15 | @media (max-width: 800px) { 16 | .nostr-container { 17 | padding: 20px 10px; 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /src/fonts/svg/telegram.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/hooks/useDevice.js: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react'; 2 | import { useSize } from 'ahooks' 3 | import { isMobileDevice } from 'lib/legacy' 4 | export default function useDevice() { 5 | const size = useSize(document.querySelector('body')); 6 | const device = useMemo(() => { 7 | return isMobileDevice() && size.width < 768 ? { 8 | isMobile: true 9 | } : { 10 | isMobile: false 11 | } 12 | }, [size.width]) 13 | return device 14 | } -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | }, 6 | parserOptions: { 7 | ecmaVersion: 12, 8 | sourceType: "module", 9 | parser: "babel-eslint", 10 | ecmaFeatures: { 11 | jsx: true, 12 | }, 13 | }, 14 | extends: ["plugin:react-hooks/recommended", "prettier"], 15 | rules: { 16 | "no-restricted-globals": 0, 17 | "no-unused-expressions": 0, 18 | "no-undef": 0, 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /.linguirc: -------------------------------------------------------------------------------- 1 | { 2 | "locales": [ 3 | "en", 4 | "zh" 5 | ], 6 | "sourceLocale": "en", 7 | "catalogs": [ 8 | { 9 | "path": "src/locales/{locale}/messages", 10 | "include": [ 11 | "src" 12 | ] 13 | } 14 | ], 15 | "formatOptions": { 16 | "lineNumbers": false 17 | }, 18 | "fallbackLocales": { 19 | "default": "en" 20 | }, 21 | "format": "po", 22 | "orderBy": "messageId", 23 | "pseudoLocale": "pseudo" 24 | } -------------------------------------------------------------------------------- /src/components/Header/HeaderLink.jsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from "react"; 2 | import { NavLink } from "react-router-dom"; 3 | import cx from "classnames"; 4 | 5 | import "./Header.scss"; 6 | 7 | export function HeaderLink({ className, exact, to, children }) { 8 | return ( 9 | 15 | {children} 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | import basicReducer from "./reducer/basicReducer"; 3 | import userReducer from "./reducer/userReducer"; 4 | import modalReducer from "./reducer/modalReducer"; 5 | import marketReducer from "./reducer/marketReducer"; 6 | const store = configureStore({ 7 | reducer: { 8 | basic: basicReducer, 9 | user: userReducer, 10 | modal: modalReducer, 11 | market: marketReducer 12 | } 13 | }); 14 | export default store; 15 | -------------------------------------------------------------------------------- /src/lib/nostr-react/utils.js: -------------------------------------------------------------------------------- 1 | export const uniqBy = (arr, key) => { 2 | return Object.values( 3 | arr.reduce( 4 | (map, item) => ({ 5 | ...map, 6 | [`${item[key]}`]: item 7 | }), 8 | {} 9 | ) 10 | ); 11 | }; 12 | 13 | export const uniqValues = (value, index, self) => { 14 | return self.indexOf(value) === index; 15 | }; 16 | 17 | export const dateToUnix = (_date) => { 18 | const date = _date || new Date(); 19 | 20 | return Math.floor(date.getTime() / 1000); 21 | }; 22 | -------------------------------------------------------------------------------- /src/pages/Deposit/DepositFormWrapper.jsx: -------------------------------------------------------------------------------- 1 | import { Button, Steps } from "antd"; 2 | import { useState, useMemo, useCallback, useEffect } from "react"; 3 | 4 | import DepositForm from "./comps/DepositForm"; 5 | 6 | /* import DepositHelpModal from "./comps/DepositHelpModal"; */ 7 | import SetupNetWorkModal from "components/Modals/SetupNetWorkModal"; 8 | export default function StepForms() { 9 | return ( 10 | <> 11 | {/* */} 12 | 13 | 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/img/success.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/img/flag_en.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/ExternalLink/ExternalLink.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import cx from "classnames"; 3 | import "./ExternalLink.scss"; 4 | 5 | /* type Props = { 6 | href: string; 7 | children: React.ReactNode; 8 | className?: string; 9 | }; */ 10 | 11 | function ExternalLink({ href, children, className }) { 12 | const classNames = cx("link-underline", className); 13 | return ( 14 | 15 | {children} 16 | 17 | ); 18 | } 19 | 20 | export default ExternalLink; 21 | -------------------------------------------------------------------------------- /src/img/ic_telegram.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/img/ic_checked.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/pages/Deposit/comps/DepositDescription.scss: -------------------------------------------------------------------------------- 1 | .deposit-description { 2 | text-decoration: none; 3 | list-style: none; 4 | width: 100%; 5 | max-width:700px; 6 | border:1px solid #333; 7 | border-radius: 10px; 8 | margin:0px auto; 9 | padding: 0 20px; 10 | &-item { 11 | padding:10px 0; 12 | font-size:14px; 13 | } 14 | } 15 | 16 | // @media screen and (max-width: 768px) { 17 | // .banner-box{ 18 | // padding: 15px; 19 | // } 20 | // .dashborad-sub-menu-box{ 21 | // display: none; 22 | // } 23 | // .market-content{ 24 | // display: none; 25 | // } 26 | // } -------------------------------------------------------------------------------- /src/img/ic_info.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | GENERATE_SOURCEMAP=false 2 | REACT_APP_NOSTR_TOKEN_SEND_TO=npub1ds8au23m9eypugda7zdnwlsv8efer0cr20dsd2ehezy5t298wspqwsnzww 3 | REACT_APP_NOSTR_MARKET_SEND_TO=npub1gfjam3thelj2d8rvkpldjjxzxghh033ztqehvyphwnd2s4uex5ss3t6r0x 4 | REACT_APP_NOSTR_CLAIMPPOINTS_SEND_TO=npub1u5apdadw28exhjurzxw5uj8e4eu7xqnqce8ffu67w63sag4yzl7qg3mlm0 5 | REACT_APP_API_GraphQL_URL=https://dev-nostrgraph.unift.xyz/api/graph_data 6 | REACT_APP_USDT_CONTRACT_ADDR=0xfad6367E97217cC51b4cd838Cc086831f81d38C2 7 | REACT_APP_BTC_SEND_TO_ADDR=bc1qzrgykyfuyredsgkw3zu4zkplkyxuv4na6mstp5 8 | REACT_APP_CURRENT_ENV=test 9 | REACT_APP_GRAPH_BASE="test_" 10 | PORT=3012 11 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | GENERATE_SOURCEMAP=false 2 | REACT_APP_NOSTR_TOKEN_SEND_TO=npub196ms9l8z22at9s4dgqwvhtm5umh5a0lrpavyv2yv5mcjhvvxy5xs4qlpcs 3 | REACT_APP_NOSTR_MARKET_SEND_TO=npub14gdjypgqm6gzrkj0emnl4z0pks3605mkc9q28vnsqwxk2znvw23s9d5g29 4 | REACT_APP_NOSTR_CLAIMPPOINTS_SEND_TO=npub1p0rggm7h8rtpaz35mjxwgd3wnmz9d743jek60zvz8wrr48nkzncsu97djg 5 | REACT_APP_API_GraphQL_URL=https://dev-nostrgraph.unift.xyz/api/graph_data 6 | REACT_APP_USDT_CONTRACT_ADDR=0xfad6367E97217cC51b4cd838Cc086831f81d38C2 7 | REACT_APP_BTC_SEND_TO_ADDR=bc1qzrgykyfuyredsgkw3zu4zkplkyxuv4na6mstp5 8 | REACT_APP_CURRENT_ENV=dev 9 | REACT_APP_GRAPH_BASE="dev_" 10 | PORT=3011 11 | -------------------------------------------------------------------------------- /src/components/Common/ConnectWalletButton.jsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | import "./ConnectWalletButton.scss"; 3 | import { Button } from "antd"; 4 | export default function ConnectWalletButton({ 5 | imgSrc, 6 | children, 7 | onClick, 8 | loading, 9 | ...props 10 | }) { 11 | return ( 12 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/img/ic_wallet_24.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/pages/Marketplace/comps/OrderDetail/index.scss: -------------------------------------------------------------------------------- 1 | .explore-detail-drawer { 2 | background: var(--color-layer-light)!important; 3 | max-height: 100%; 4 | } 5 | .explore-detail-list{ 6 | &-item { 7 | display: flex; 8 | padding-bottom:20px; 9 | &__label{ 10 | color:var(--color-text-base); 11 | font-size:14px; 12 | padding-right:10px; 13 | width:160px; 14 | } 15 | &__text { 16 | color:var(--color-text-light); 17 | font-size: 14px; 18 | word-break: break-all; 19 | flex:1; 20 | } 21 | } 22 | .border-t{ 23 | border-top: 1px solid #575454; 24 | padding-top: 20px; 25 | } 26 | } -------------------------------------------------------------------------------- /src/pages/Explore/comps/ExploreDetails/index.scss: -------------------------------------------------------------------------------- 1 | .explore-detail-drawer { 2 | background: var(--color-layer-light)!important; 3 | max-height: 100%; 4 | } 5 | .explore-detail-list{ 6 | &-item { 7 | display: flex; 8 | padding-bottom:20px; 9 | align-items: center; 10 | &__label{ 11 | color:var(--color-text-base); 12 | font-size:14px; 13 | padding-right:10px; 14 | width:160px; 15 | } 16 | &__text { 17 | color:var(--color-text-light); 18 | font-size: 14px; 19 | word-break: break-all; 20 | flex:1; 21 | } 22 | } 23 | .border-t{ 24 | border-top: 1px solid #575454; 25 | padding-top: 20px; 26 | } 27 | } -------------------------------------------------------------------------------- /src/pages/PageNotFound/PageNotFound.css: -------------------------------------------------------------------------------- 1 | .page-not-found-container { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | min-height: 60vh; 6 | margin-bottom: 7rem; 7 | } 8 | 9 | .page-not-found { 10 | text-align: center; 11 | } 12 | .page-not-found img { 13 | max-width: 225px; 14 | } 15 | .go-back span { 16 | color: #a0a3c4; 17 | } 18 | .go-back a { 19 | color: white; 20 | } 21 | .go-back .history-go{ 22 | text-decoration: underline; 23 | cursor: pointer; 24 | color: white; 25 | } 26 | @media (max-width: 500px) { 27 | .page-not-found img { 28 | max-width: 200px; 29 | } 30 | .page-not-found { 31 | margin-top: 2.5rem; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.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 | .vscode 8 | 9 | # lingui 10 | # /src/locales/**/*.js 11 | # /src/locales/en/messages.po 12 | # /src/locales/_build/ 13 | 14 | # testing 15 | /coverage 16 | 17 | # production 18 | /build 19 | 20 | # misc 21 | .DS_Store 22 | .env.local 23 | .env.development.local 24 | .env.test.local 25 | .env.production.local 26 | 27 | npm-debug.log* 28 | yarn-debug.log* 29 | yarn-error.log* 30 | 31 | package-lock.json 32 | .yarn 33 | .yarn/* 34 | !.yarn/patches 35 | !.yarn/plugins 36 | !.yarn/releases 37 | !.yarn/sdks 38 | !.yarn/versions 39 | /.idea/ 40 | -------------------------------------------------------------------------------- /src/hooks/useNostrDisconnect.js: -------------------------------------------------------------------------------- 1 | import { useDisconnect } from "wagmi"; 2 | import { setAccount, setConnectPlat, setSelectedTokenPlatForm } from "store/reducer/userReducer"; 3 | import { useDispatch } from "react-redux"; 4 | import { useCallback } from "react"; 5 | import * as Lockr from "lockr"; 6 | export default function useNostrDisconnect() { 7 | const { disconnect } = useDisconnect(); 8 | const dispatch = useDispatch(); 9 | const handleDisconnect = useCallback(() => { 10 | Lockr.set("connectPlat", ""); 11 | dispatch(setConnectPlat("")); 12 | dispatch(setAccount("")); 13 | disconnect(); 14 | }, [disconnect, dispatch]); 15 | return { 16 | handleDisconnect 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /src/store/reducer/marketReducer.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | export const marketSlice = createSlice({ 3 | name: "market", 4 | initialState: { 5 | tokenList: [], 6 | responseTime: 0 7 | }, 8 | reducers: { 9 | setTokenList(state, { payload }) { 10 | if (payload && Array.isArray(payload)) { 11 | const sortedArray = [...payload].sort((a, b) => a.id - b.id); 12 | state.tokenList = sortedArray; 13 | } 14 | }, 15 | setResponseTime(state, { payload }) { 16 | state.responseTime = payload; 17 | } 18 | } 19 | }); 20 | export const { setTokenList, setResponseTime } = marketSlice.actions; 21 | export default marketSlice.reducer; 22 | -------------------------------------------------------------------------------- /src/lib/utils/userAgent.js: -------------------------------------------------------------------------------- 1 | const userAgent = navigator.userAgent; 2 | const reChrome = /chrome/i; 3 | const reSafari = /safari/i; 4 | const reTokenpocket = /tokenpocket/i; 5 | const reMetamask = /metamask/i; 6 | export const isInChrome = reChrome.test(userAgent); 7 | export const isInSafari = reSafari.test(userAgent); 8 | export const isInMetamask = reMetamask.test(userAgent); 9 | export const isInTokenPocket = () => { 10 | return reTokenpocket.test(userAgent) || window.ethereum?.isTokenPocket; 11 | }; 12 | export const isMobile = () => { 13 | return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent); 14 | }; 15 | 16 | export const isApple = () => { 17 | return /iPhone|iPad|iPod/i.test(userAgent); 18 | }; 19 | -------------------------------------------------------------------------------- /src/hooks/unisatWallet/useGetFees.js: -------------------------------------------------------------------------------- 1 | /* import mempoolJS from "@mempool/mempool.js"; */ 2 | import { useState, useEffect } from "react"; 3 | import { useRequest } from "ahooks"; 4 | 5 | const getNostrFeesRecommendFee = async () => { 6 | const fees = mempoolJS().bitcoin.fees; 7 | const ret = await fees.getFeesRecommended(); 8 | 9 | return ret; 10 | }; 11 | 12 | export const useGetRecommendFee = (ready = false) => { 13 | const { data, run, loading } = useRequest(getNostrFeesRecommendFee, { 14 | pollingInterval: 5000, 15 | ready, 16 | manual: true 17 | }); 18 | 19 | useEffect(() => { 20 | if (ready) { 21 | run(); 22 | } 23 | }, [ready, run]); 24 | 25 | return { 26 | feesRecommended: data 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /src/img/ic_twitter.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/config/constants.js: -------------------------------------------------------------------------------- 1 | export const TOKEN_LIST = [ 2 | { symbol: "USDT", value: process.env.REACT_APP_USDT_CONTRACT_ADDR, platform: "ETH" }, 3 | { 4 | symbol: "ordi", 5 | value: "ordi", 6 | platform: "BTC" 7 | }, 8 | { 9 | symbol: "meme", 10 | value: "meme", 11 | platform: "BTC" 12 | }, 13 | { 14 | symbol: "punk", 15 | value: "punk", 16 | platform: "BTC" 17 | }, 18 | { 19 | symbol: "pepe", 20 | value: "pepe", 21 | platform: "BTC" 22 | }, 23 | { 24 | symbol: "gold", 25 | value: "gold", 26 | platform: "BTC" 27 | }, 28 | { 29 | symbol: "lvdi", 30 | value: "lvdi", 31 | platform: "BTC" 32 | } 33 | ]; 34 | export const BTCEXPORE_PREFIX = "https://explorer.btc.com/btc/transaction/"; 35 | -------------------------------------------------------------------------------- /src/fonts/svg/twitter.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pages/Deposit/comps/Inscription.scss: -------------------------------------------------------------------------------- 1 | .inscription-card { 2 | width:100px; 3 | height:140px; 4 | &-radio { 5 | padding:0 5px; 6 | .ant-radio-inner { 7 | width:14px; 8 | height:14px; 9 | } 10 | } 11 | .ant-card-body { 12 | width:100%; 13 | height:100%; 14 | padding: 0px; 15 | display: flex; 16 | flex-direction: column; 17 | 18 | .inscription-name,.inscription-num { 19 | height:20px; 20 | padding:0px 10px; 21 | } 22 | .inscription-num { 23 | background-color: var(--color-loading-bar-shine); 24 | padding:0 10px; 25 | } 26 | .inscription-amount { 27 | flex:auto; 28 | display: flex; 29 | justify-content: center; 30 | align-items: center; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/img/flag_zh.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/pages/Testnet/comps/NotConnectContainer.jsx: -------------------------------------------------------------------------------- 1 | import ConnectNostr from "components/Common/ConnectNostr"; 2 | 3 | export default function NotConnectContainer() { 4 | return ( 5 |
6 |
7 |
8 | Please connect your Nostr account to view the data. 9 |
10 |

11 | For web users, use the Alby Wallet extension to connect. For Token 12 | Pocket app users, you can connect using the in-app DApp browser. 13 |

14 |
15 | 16 |
17 |
18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /src/components/NetworkDropdown/index.scss: -------------------------------------------------------------------------------- 1 | .nostr-network-dropdown { 2 | .ant-btn { 3 | border:none; 4 | } 5 | .ant-btn + .ant-btn { 6 | margin-left:0; 7 | border-left:1px solid #424242; 8 | } 9 | 10 | } 11 | .nostr-network-list { 12 | .ant-dropdown-menu { 13 | .ant-dropdown-menu-item { 14 | padding:10px 14px; 15 | } 16 | } 17 | } 18 | .network-dropdown-chain-name__active { 19 | position:relative; 20 | &::after { 21 | content:''; 22 | width: 6px; 23 | height: 6px; 24 | border-radius: 3px; 25 | background: var(--color-green); 26 | position: absolute; 27 | right:-10px; 28 | top:50%; 29 | margin-top:-3px; 30 | } 31 | } 32 | @media screen and (max-width: 768px) { 33 | .App-header-network{ 34 | display: none; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/components/EllipsisMiddle/index.jsx: -------------------------------------------------------------------------------- 1 | import { Typography } from "antd"; 2 | const { Text } = Typography; 3 | const EllipsisMiddle = ({ 4 | suffixCount, 5 | children, 6 | copyable = true, 7 | suffixCountMore = 2, 8 | suffixEnable = true, 9 | }) => { 10 | const formatChildren = suffixEnable 11 | ? `${children.substring( 12 | 0, 13 | suffixCount + suffixCountMore 14 | )}...${children.substring(children.length - suffixCount)}` 15 | : children; 16 | return ( 17 | 29 | {formatChildren} 30 | 31 | ); 32 | }; 33 | export default EllipsisMiddle; 34 | -------------------------------------------------------------------------------- /src/pages/Withdraw/index.jsx: -------------------------------------------------------------------------------- 1 | import WithdrawForm from "./comps/WithdrawForm"; 2 | import { LeftOutlined } from "@ant-design/icons"; 3 | import { useHistory } from "react-router-dom"; 4 | import "./index.scss"; 5 | export default function Withdraw() { 6 | const history = useHistory(); 7 | return ( 8 | <> 9 |
10 |
11 |
{ 14 | history.push("/account"); 15 | }} 16 | > 17 | 18 | Send Assets 19 |
20 | 21 |
22 |
23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/components/Common/UniftTabs/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Tabs } from "antd"; 3 | import "./index.scss"; 4 | export default function UniftTabs({ ...props }) { 5 | const size = props.size ? props.size : "middle"; 6 | const defaultActiveKey = props.defaultActiveKey || "1"; 7 | const items = new Array(2).fill(null).map((_, i) => { 8 | const id = String(i + 1); 9 | return { 10 | label: `Tab ${id}`, 11 | key: id, 12 | children: `Content of tab ${id}`, 13 | }; 14 | }); 15 | const dataItems = props.items || items; 16 | return ( 17 | <> 18 | 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/hooks/useSignMessage.js: -------------------------------------------------------------------------------- 1 | import { useCallback } from "react"; 2 | import { useSignMessage } from "wagmi"; 3 | import * as Lockr from "lockr"; 4 | import { message as Message } from "antd"; 5 | export default function useNostrSignMessage(isNeedPrefix = true) { 6 | const { signMessageAsync } = useSignMessage(); 7 | const signMessage = useCallback( 8 | async (message) => { 9 | const connectPlat = Lockr.get("connectPlat"); 10 | const willSignMessage = isNeedPrefix ? `Nostr\n\n${message}` : `${message}`; 11 | if (connectPlat === "ETH" || !connectPlat) { 12 | return await signMessageAsync({ message: willSignMessage }); 13 | } else { 14 | return await window.unisat.signMessage(willSignMessage); 15 | } 16 | }, 17 | [isNeedPrefix, signMessageAsync] 18 | ); 19 | return signMessage; 20 | } 21 | -------------------------------------------------------------------------------- /src/service/index.js: -------------------------------------------------------------------------------- 1 | export const getFeeSummary = async () => { 2 | return await ( 3 | await fetch("https://unisat.io/wallet-api-v4/default/fee-summary", { 4 | headers: { 5 | accept: "*/*", 6 | "accept-language": "zh-CN,zh;q=0.9", 7 | "cache-control": "no-cache", 8 | pragma: "no-cache", 9 | "sec-fetch-dest": "empty", 10 | "sec-fetch-mode": "cors", 11 | "sec-fetch-site": "none", 12 | "x-address": "bc1qzrgykyfuyredsgkw3zu4zkplkyxuv4na6mstp5", 13 | "x-channel": "store", 14 | "x-client": "UniSat Wallet", 15 | "x-udid": "zkkfXpanHmqw", 16 | "x-version": "1.1.21" 17 | }, 18 | referrerPolicy: "strict-origin-when-cross-origin", 19 | body: null, 20 | method: "GET", 21 | mode: "cors", 22 | credentials: "include" 23 | }) 24 | ).json(); 25 | }; 26 | -------------------------------------------------------------------------------- /src/components/Common/ConnectWalletButton.scss: -------------------------------------------------------------------------------- 1 | .connect-wallet-btn { 2 | background: transparent; 3 | // padding: 0.5rem 1.4rem; 4 | height: 36px; 5 | margin-left: 2.4rem; 6 | display: inline-flex; 7 | align-items: center; 8 | border: none; 9 | color: #fff; 10 | font-size: var(--font-sm); 11 | cursor: pointer; 12 | border: 1px solid var(--color-unift-green); 13 | border-radius: var(--border-radius-sm); 14 | 15 | .btn-icon { 16 | display: inline-flex; 17 | align-items: center; 18 | justify-content: center; 19 | width:18px; 20 | height:18px; 21 | } 22 | .btn-label { 23 | font-weight: 400; 24 | font-size: var(--font-sm); 25 | margin-left: 0.8rem; 26 | letter-spacing: 0; 27 | } 28 | &:hover { 29 | background: var(--dark-blue-hover); 30 | } 31 | &:active { 32 | background: var(--dark-blue-active); 33 | } 34 | } 35 | 36 | 37 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": false, 10 | "noImplicitAny": false, 11 | "strictNullChecks": true, 12 | "strictFunctionTypes": true, 13 | "strictBindCallApply": true, 14 | "noImplicitThis": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "module": "esnext", 18 | "moduleResolution": "node", 19 | "resolveJsonModule": true, 20 | "isolatedModules": true, 21 | "noEmit": true, 22 | "jsx": "react-jsx", 23 | "baseUrl": "./src" 24 | }, 25 | "include": ["src"], 26 | "exclude": [ 27 | "node_modules", 28 | "**/node_modules/**", 29 | "src/charting_library" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /src/components/OutLinks/index.jsx: -------------------------------------------------------------------------------- 1 | import "./index.scss"; 2 | import { ReactComponent as Twitter } from "fonts/svg/twitter.svg"; 3 | import { ReactComponent as Telegram } from "fonts/svg/telegram.svg"; 4 | import { ReactComponent as Gitbook } from "fonts/svg/gitbook.svg"; 5 | export default function OutLinks() { 6 | return ( 7 | <> 8 |
9 | {/* 10 | 11 | 12 | 13 | 14 | */} 15 | 16 | 17 | 18 |
19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/pages/Deposit/comps/DepositDescription.jsx: -------------------------------------------------------------------------------- 1 | import "./DepositDescription.scss"; 2 | export default function DepositDescription() { 3 | return ( 4 | <> 5 |
    6 |
  • 7 | 1. We currently support deposit ERC20 USDT and some BRC20 Tokens. 8 |
  • 9 |
  • 10 | 2. Please be sure to confirm that your receiving Nostr address is 11 | correct. Nostr addresses are generally obtained from some Nostr 12 | clients or wallets that support Nostr Protocol. 13 |
  • 14 |
  • 15 | 3. All deposit transactions are transparent, and you can check and pay 16 | attention to the deposit status in real time; 17 |
  • 18 |
19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/lib/i18n.js: -------------------------------------------------------------------------------- 1 | import { i18n } from "@lingui/core"; 2 | // import { en, es, zh, ko, ru, ja, fr, de } from "make-plural/plurals"; 3 | import { en, zh } from "make-plural/plurals"; 4 | import { LANGUAGE_LOCALSTORAGE_KEY } from "config/localStorage"; 5 | 6 | // uses BCP-47 codes from https://unicode-org.github.io/cldr-staging/charts/latest/supplemental/language_plural_rules.html 7 | export const locales = { 8 | en: "English", 9 | zh: "Chinese" 10 | }; 11 | 12 | export const defaultLocale = "en"; 13 | 14 | i18n.loadLocaleData({ 15 | en: { plurals: en }, 16 | zh: { plurals: zh } 17 | }); 18 | 19 | export function isTestLanguage(locale) { 20 | return locale === "pseudo"; 21 | } 22 | export async function dynamicActivate(locale) { 23 | const { messages } = await import(`locales/${locale}/messages.po`); 24 | if (!isTestLanguage(locale)) { 25 | localStorage.setItem(LANGUAGE_LOCALSTORAGE_KEY, locale); 26 | } 27 | i18n.load(locale, messages); 28 | i18n.activate(locale); 29 | } 30 | -------------------------------------------------------------------------------- /src/pages/Deposit/comps/Inscription.jsx: -------------------------------------------------------------------------------- 1 | import { Card } from "antd"; 2 | import { useState, useEffect } from "react"; 3 | import { Radio } from "antd"; 4 | const { Meta } = Card; 5 | import "./Inscription.scss"; 6 | export default function Inscription({ inscription, onInscriptionChange }) { 7 | const handleToggleChecked = () => { 8 | onInscriptionChange(inscription.inscriptionId, !inscription.checked); 9 | }; 10 | 11 | return ( 12 | <> 13 | 18 | 23 |
{inscription?.tick}
24 |
{inscription?.amt}
25 |
#{inscription?.inscriptionNumber}
26 |
27 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Luke 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/img/ic_btc_40.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pages/Deposit/index.jsx: -------------------------------------------------------------------------------- 1 | import "./index.scss"; 2 | import DepositDescription from "./comps/DepositDescription"; 3 | import DepositFormWrapper from "./DepositFormWrapper"; 4 | import { LeftOutlined } from "@ant-design/icons"; 5 | import { useHistory } from "react-router-dom"; 6 | export default function Deposit() { 7 | const history = useHistory(); 8 | return ( 9 | <> 10 |
11 | {/*
12 | 13 |
*/} 14 |
15 |
{ 18 | history.push("/account"); 19 | }} 20 | > 21 | 22 | Receive Assets 23 |
24 | 25 | {/* */} 26 |
27 |
28 | 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | import "regenerator-runtime/runtime"; 4 | import { BrowserRouter as Router } from "react-router-dom"; 5 | import App from "./App/App"; 6 | import { ConfigProvider, theme } from "antd"; 7 | import { Provider as ReduxProvider } from "react-redux"; 8 | 9 | import store from "./store"; 10 | 11 | import "styles/global.scss"; 12 | const container = document.getElementById("root"); 13 | const root = createRoot(container); 14 | root.render( 15 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ); 30 | 31 | // If you want to start measuring performance in your app, pass a function 32 | // to log results (for example: reportWebVitals(console.info)) 33 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 34 | //reportWebVitals(); 35 | -------------------------------------------------------------------------------- /src/components/Modals/LanguageModal.jsx: -------------------------------------------------------------------------------- 1 | import { Modal, Row } from "antd"; 2 | import { useSelector, useDispatch } from "react-redux"; 3 | import { useCallback, useMemo, useState } from "react"; 4 | import { setLanguageModalVisible } from "store/reducer/modalReducer"; 5 | import LanguageModalContent from "components/NetworkDropdown/LanguageModalContent"; 6 | import { t } from "@lingui/macro"; 7 | import "./index.scss"; 8 | export default function LanguageModal() { 9 | const { languageModalVisible } = useSelector(({ modal }) => modal); 10 | const dispatch = useDispatch(); 11 | const onCancel = useCallback(() => { 12 | dispatch(setLanguageModalVisible(false)); 13 | }, [dispatch]); 14 | return ( 15 | <> 16 | 25 | <> 26 | 27 | 28 | 29 | 30 | 31 | 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /src/lib/url.js: -------------------------------------------------------------------------------- 1 | export function getRootUrl() { 2 | const { origin, protocol, hostname, port } = window.location; 3 | let url = origin; 4 | if (!origin) { 5 | const portString = port && `:${port}`; 6 | url = `${protocol}//${hostname}${portString}`; 7 | } 8 | return url; 9 | } 10 | export function getQueryParam(key) { 11 | if (!key) { 12 | return false; 13 | } 14 | 15 | var value = ""; 16 | var paramStr = window.location.search ? window.location.search.substr(1) : ""; 17 | 18 | if (paramStr) { 19 | paramStr.split("&").forEach(function (param) { 20 | var arr = param.split("="); 21 | if (arr[0] == key) { 22 | value = arr[1]; 23 | } 24 | }); 25 | } 26 | 27 | return value; 28 | } 29 | export function getQueryVariable(variable) { 30 | var str = window.location.hash.split("?"); 31 | var query = str[1]; 32 | if (query) { 33 | var vars = query.split("&"); 34 | for (var i = 0; i < vars.length; i++) { 35 | var pair = vars[i].split("="); 36 | if (pair[0] == variable) { 37 | return pair[1]; 38 | } 39 | } 40 | } 41 | return ""; 42 | } 43 | -------------------------------------------------------------------------------- /src/components/Header/AppNostrHeaderUser.jsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useEffect, memo, useMemo } from "react"; 2 | 3 | import "./Header.scss"; 4 | 5 | import ConnectNostr from "components/Common/ConnectNostr"; 6 | 7 | import { useSelector, useDispatch } from "react-redux"; 8 | import AddressNostrDropdown from "components/AddressDropdown/AddressNostrDropdown"; 9 | function AppNostrHeaderUser() { 10 | const { nostrAccount } = useSelector(({ user }) => user); 11 | const memoAddressNostrDropdown = useMemo(() => { 12 | return ; 13 | }, []); 14 | if (!nostrAccount) { 15 | return ( 16 |
17 | { 18 | <> 19 | 20 | 21 | } 22 |
23 | ); 24 | } 25 | 26 | return ( 27 |
28 | { 29 | <> 30 |
31 | {memoAddressNostrDropdown} 32 |
33 | 34 | {/* */} 35 | 36 | } 37 |
38 | ); 39 | } 40 | export default memo(AppNostrHeaderUser); 41 | -------------------------------------------------------------------------------- /src/components/Modals/OnlyMobileSupportModal.jsx: -------------------------------------------------------------------------------- 1 | import { Modal, Button, Row, Col } from "antd"; 2 | import { setOnlyMobileSupportedVisible } from "store/reducer/modalReducer"; 3 | import { useDispatch, useSelector } from "react-redux"; 4 | import { useCallback } from "react"; 5 | import "./index.scss"; 6 | export default function OnlyMobileSupportModal() { 7 | const { onlyMobileSupportedVisible } = useSelector(({ modal }) => modal); 8 | 9 | const dispatch = useDispatch(); 10 | const onCancel = useCallback(() => { 11 | dispatch(setOnlyMobileSupportedVisible(false)); 12 | }, [dispatch]); 13 | return ( 14 | 21 | 22 |

23 | Lightning Network and Taproot receive assets and send assets are not 24 | supported on mobile currently, please go to the Web to operate. 25 |

26 | 29 |
30 |
31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /src/pages/Account/comps/AddressBook/index.scss: -------------------------------------------------------------------------------- 1 | .addressBook-modal,.add-address-modal{ 2 | .add-address-form{ 3 | padding-top: 20px; 4 | } 5 | .ant-modal-title{ 6 | padding-bottom: 10px; 7 | text-align: center; 8 | } 9 | .addressBook-empty{ 10 | text-align: center; 11 | padding-top: 30px; 12 | } 13 | .add-addres-btn-box{ 14 | text-align: center; 15 | // padding: 20px 0; 16 | margin-top: 30px; 17 | .add-addres-btn{ 18 | min-width: 180px; 19 | } 20 | } 21 | .addressBook-content{ 22 | padding-top: 10px; 23 | text-align: center; 24 | min-height: 100px; 25 | max-height: 300px; 26 | } 27 | .address-items{ 28 | display: flex; 29 | justify-content: space-between; 30 | padding: 0 50px; 31 | line-height: 30px; 32 | } 33 | .address-delete{ 34 | cursor: pointer; 35 | &:hover{ 36 | color: var(--color-green-light); 37 | } 38 | } 39 | } 40 | @media screen and (max-width: 768px) { 41 | .addressBook-modal,.add-address-modal{ 42 | .address-items{ 43 | display: flex; 44 | justify-content: space-between; 45 | padding: 0; 46 | line-height: 30px; 47 | } 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/components/Common/UniftTabs/index.scss: -------------------------------------------------------------------------------- 1 | .unift-tabs { 2 | height:100%; 3 | .ant-tabs-ink-bar{ 4 | top:0; 5 | bottom: auto; 6 | background: var(--color-green-light); 7 | } 8 | .ant-tabs-nav { 9 | margin:0; 10 | &:before{ 11 | border: none; 12 | } 13 | .ant-tabs-nav-operations{ 14 | display: none; 15 | } 16 | } 17 | .ant-tabs-nav-list { 18 | width:100%; 19 | display: flex; 20 | .ant-tabs-tab { 21 | flex:1; 22 | // width: 45%; 23 | // border: 1px solid #2d2d3d; 24 | border-top:none; 25 | border-radius:0!important; 26 | margin-left:0!important; 27 | box-sizing: border-box; 28 | // overflow: hidden; 29 | .ant-tabs-tab-btn { 30 | width:100%; 31 | text-align: center; 32 | color:var(--color-text-dark); 33 | } 34 | &-active { 35 | // border-bottom: 1px solid #2d2d3d!important; 36 | .ant-tabs-tab-btn { 37 | color:var(--color-text-light); 38 | } 39 | } 40 | 41 | 42 | } 43 | } 44 | .ant-tabs-content { 45 | height:100%; 46 | } 47 | .ant-tabs-tabpane { 48 | height:100%; 49 | } 50 | .ant-tabs-tab-btn { 51 | font-size: 14px;; 52 | } 53 | } -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | NostrAssets 17 | 18 | 19 | 20 |
21 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/pages/PageNotFound/PageNotFound.js: -------------------------------------------------------------------------------- 1 | import SEO from "components/Common/SEO"; 2 | import Footer from "components/Footer/Footer"; 3 | import { getPageTitle } from "lib/legacy"; 4 | import "./PageNotFound.css"; 5 | import { Trans } from "@lingui/macro"; 6 | 7 | import { useHistory } from "react-router-dom"; 8 | function PageNotFound() { 9 | const history = useHistory(); 10 | const historyGo = (url) => { 11 | history.push(url); 12 | }; 13 | return ( 14 | // 15 |
16 |
17 |
18 |

19 | Page not found 20 |

21 |

22 | 23 | Return to 24 | {/* Homepage or Trade */} 25 | historyGo("/explore")}> 26 | Homepage 27 | 28 | 29 |

30 |
31 |
32 | {/*
*/} 33 |
34 | //
35 | ); 36 | } 37 | 38 | export default PageNotFound; 39 | -------------------------------------------------------------------------------- /src/fonts/svg/gitbook.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/lib/nostr-react/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | ProjectName: nostr-react 4 | Version: 0.7.0 5 | 6 | 7 | MIT License 8 | 9 | Copyright (c) 2022 Tristan Edwards 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in all 19 | copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | SOFTWARE. 28 | */ 29 | export * from "./utils"; 30 | export * from "./core"; 31 | -------------------------------------------------------------------------------- /src/img/Receive.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/Modals/ConnectNostrModal.jsx: -------------------------------------------------------------------------------- 1 | import { Modal, Timeline, Row, Col, Button } from "antd"; 2 | import { useSelector, useDispatch } from "react-redux"; 3 | import { useCallback, useEffect, useMemo, useState } from "react"; 4 | import { setNostrModalVisible } from "store/reducer/modalReducer"; 5 | import ConnectNostr from "components/Common/ConnectNostr"; 6 | 7 | import "./index.scss"; 8 | export default function ConnectNostrModal() { 9 | const { nostrModalVisible } = useSelector(({ modal }) => modal); 10 | 11 | const dispatch = useDispatch(); 12 | const onCancel = useCallback(() => { 13 | dispatch(setNostrModalVisible(false)); 14 | }, [dispatch]); 15 | 16 | return ( 17 | <> 18 | {nostrModalVisible && ( 19 | 28 |
29 |

30 | You are not connected yet. Please use “Connect Nostr” to sign in with your Nostr account. 31 |

32 | 33 |
34 |
35 | )} 36 | 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /src/img/addressBook.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/Common/Modal/Modal.jsx: -------------------------------------------------------------------------------- 1 | import { Modal } from "antd"; 2 | import { useState, useImperativeHandle, forwardRef } from "react"; 3 | function BaseModal({ ...props }, ref) { 4 | const [isModalOpen, setIsModalOpen] = useState(false); 5 | 6 | const showModal = () => { 7 | setIsModalOpen(true); 8 | }; 9 | const handleOk = () => { 10 | setIsModalOpen(false); 11 | }; 12 | const handleCancel = () => { 13 | setIsModalOpen(false); 14 | if (props.initForm) { 15 | props.initForm(); 16 | } 17 | if (props.onCancel) { 18 | props.onCancel(); 19 | } 20 | }; 21 | const getModalStatus = () => { 22 | return isModalOpen; 23 | }; 24 | useImperativeHandle(ref, () => { 25 | return { 26 | showModal: showModal, 27 | handleCancel: handleCancel, 28 | handleOk: handleOk, 29 | getModalStatus 30 | }; 31 | }); 32 | return ( 33 | <> 34 | {isModalOpen ? ( 35 | 44 | {props.children} 45 | 46 | ) : null} 47 | 48 | ); 49 | } 50 | export default forwardRef(BaseModal); 51 | -------------------------------------------------------------------------------- /src/pages/Testnet/comps/ClaimDescription.jsx: -------------------------------------------------------------------------------- 1 | export default function ClaimDescription() { 2 | return ( 3 | <> 4 |
    5 |
  • 6 | 1. How to claim Testnet tokens? 7 |
    8 | - For web users, use the Alby Wallet extension to connect your Nostr 9 | account and claim. 10 |
    11 |
    12 | - For mobile users, use Damus or Amethyst app and send a command to 13 | NostrAssets Token Manager. Use the command “Claim”. 14 |
    15 |
    16 | - For Token Pocket users, use the DApp browser to connect your Nostr 17 | account and claim. 18 |
    19 |
  • 20 | {/*
  • 21 | 2. Upon claiming successfully, your NostrAssets account will 22 | automatically receive testnet tokens. (10000 USDT 、10000 ORDI 、10000 23 | OXBT、10000 PEPE、10000 BTOC、10000 VMPX) 24 |
  • */} 25 |
  • 26 | 2. Each Nostr address can only claim once. 27 |
  • 28 |
29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /src/pages/Explore/comps/TokenSlider/index.scss: -------------------------------------------------------------------------------- 1 | .token-slider-box{ 2 | // padding: 0; 3 | overflow: hidden; 4 | height: 40px; 5 | padding: 0 50px; 6 | background: linear-gradient(180deg, rgba(128, 255, 255, 1) 0%, rgba(250, 205, 145, 1) 100%); 7 | position: relative; 8 | } 9 | 10 | .token-slider{ 11 | 12 | .slider-item{ 13 | white-space: nowrap; 14 | // display: inline; 15 | font-size: 16px; 16 | color: #333; 17 | padding: 12px 0; 18 | // margin-right: 20px; 19 | width: 300px !important; 20 | // display: flex; 21 | .name{ 22 | font-size: 16px; 23 | font-weight: 700; 24 | } 25 | .value{ 26 | font-size: 18px; 27 | font-weight: 700; 28 | 29 | } 30 | .rate{ 31 | color: #D9001B; 32 | } 33 | } 34 | } 35 | @keyframes mymove { 36 | from { 37 | transform: translateX(0); 38 | } 39 | to { 40 | transform: translateX(-80%); 41 | } 42 | } 43 | @media screen and (max-width: 768px) { 44 | .token-slider-box{ 45 | // padding: 0; 46 | overflow: hidden; 47 | height: 40px; 48 | padding: 0 20px; 49 | } 50 | .token-slider{ 51 | .slider-item{ 52 | font-size: 14px; 53 | .name{ 54 | font-size: 14px; 55 | font-weight: 700; 56 | } 57 | .value{ 58 | font-size: 14px; 59 | font-weight: 700; 60 | 61 | } 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /src/components/Common/ConnectNostr.jsx: -------------------------------------------------------------------------------- 1 | import ConnectWalletButton from "./ConnectWalletButton"; 2 | import logo from "img/logo.svg"; 3 | import "./ConnectWallet.scss"; 4 | 5 | import { useCallback, useEffect, useState } from "react"; 6 | import useGetNostrAccount from "hooks/useGetNostrAccount"; 7 | // import { useQueryBalance } from "hooks/useNostrMarket"; 8 | // import { nip19 } from "nostr-tools"; 9 | export default function ConnectNostr() { 10 | const { handleGetNostrAccount } = useGetNostrAccount(); 11 | // const { handleQueryBalance } = useQueryBalance(); 12 | const [loading, setLoading] = useState(false); 13 | const handleConnectNostr = useCallback(async () => { 14 | setLoading(true); 15 | const nostrAccount = await handleGetNostrAccount().catch((e) => { 16 | window._message.open({ 17 | type: "error", 18 | content: e.message, 19 | }); 20 | }); 21 | if (nostrAccount) { 22 | window._message.open({ 23 | type: "success", 24 | content: "Connect success.", 25 | }); 26 | } 27 | setLoading(false); 28 | }, [handleGetNostrAccount]); 29 | return ( 30 |
31 | 36 | Connect Nostr 37 | 38 |
39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /src/components/Modals/index.scss: -------------------------------------------------------------------------------- 1 | .create-nostr-modal { 2 | padding: 10px 10px 0; 3 | } 4 | .ant-timeline .ant-timeline-item-head { 5 | background-color: transparent; 6 | } 7 | .connect-nostr-modal { 8 | .ant-modal-title { 9 | padding-bottom: 10px; 10 | } 11 | &-description { 12 | font-size: 12px; 13 | padding-top: 10px; 14 | &:last-of-type { 15 | padding-top: 0px; 16 | } 17 | } 18 | &-btn { 19 | text-align: center; 20 | font-size: 14px; 21 | padding-top: 10px; 22 | button { 23 | color: var(--color-unift-green); 24 | } 25 | } 26 | &-link { 27 | font-size: 12px; 28 | a { 29 | color: var(--color-unift-green); 30 | } 31 | 32 | text-align: center; 33 | padding-top: 20px; 34 | } 35 | } 36 | .nostr-modal { 37 | &-content { 38 | padding-top: 20px; 39 | } 40 | &-description { 41 | text-align: center; 42 | color: var(--color-text-base); 43 | font-size: 14px; 44 | } 45 | } 46 | .turnon-img-flex { 47 | display: flex; 48 | .img-box-item { 49 | flex: 1; 50 | img { 51 | width: 100%; 52 | border-radius: 5px; 53 | } 54 | } 55 | .img-box-item + .img-box-item { 56 | margin-left: 10px; 57 | } 58 | } 59 | .nostr-setup-modal__content { 60 | p { 61 | text-align: left; 62 | padding-top: 10px; 63 | } 64 | button { 65 | margin-top: 20px; 66 | } 67 | padding: 20px 0px 10px; 68 | text-align: center; 69 | } 70 | -------------------------------------------------------------------------------- /src/img/Send.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pages/Withdraw/index.scss: -------------------------------------------------------------------------------- 1 | .withdraw-container { 2 | flex: 1; 3 | padding: 20px 20px 60px; 4 | max-width: 100%; 5 | .withdraw-content { 6 | margin-top: 20px; 7 | border: 1px solid var(--color-border-grey); 8 | border-radius: 20px; 9 | padding-bottom: 20px; 10 | } 11 | .withdraw-title { 12 | margin-top: 40px; 13 | padding-left: 20px; 14 | text-align: left; 15 | font-size: 18px; 16 | font-weight: bold; 17 | cursor: pointer; 18 | &__value { 19 | padding-left: 20px; 20 | } 21 | } 22 | .withdraw-send-btn { 23 | width: 120px; 24 | } 25 | } 26 | .step-container { 27 | width: 400px; 28 | margin: 0 auto; 29 | margin-top: 30px; 30 | } 31 | @media screen and (max-width: 768px) { 32 | .step-container { 33 | width: 100%; 34 | margin: 0 auto; 35 | margin-top: 30px; 36 | } 37 | .withdraw-form .deposit-balance { 38 | padding-left: 0; 39 | } 40 | .withdraw-banner-box { 41 | display: none; 42 | } 43 | .withdraw-container { 44 | .withdraw-content { 45 | margin-top: 0; 46 | border: none; 47 | } 48 | .withdraw-title { 49 | margin-top: 0; 50 | padding-left: 0; 51 | text-align: left; 52 | font-size: 18px; 53 | font-weight: bold; 54 | cursor: pointer; 55 | &__value { 56 | padding-left: 20px; 57 | } 58 | } 59 | // .banner { 60 | // display: none; 61 | // } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/store/reducer/modalReducer.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | export const modalSlice = createSlice({ 3 | name: "modal", 4 | initialState: { 5 | nostrModalVisible: false, 6 | connectNostrModalVisible: false, 7 | walletConnectModalVisible: false, 8 | languageModalVisible: false, 9 | turnOnNostrDrawerVisible: false, 10 | onlyMobileSupportedVisible: false 11 | }, 12 | reducers: { 13 | setNostrModalVisible(state, action) { 14 | state.nostrModalVisible = action.payload; 15 | }, 16 | setWalletConnectModalVisible(state, action) { 17 | state.walletConnectModalVisible = action.payload; 18 | }, 19 | setLanguageModalVisible(state, action) { 20 | state.languageModalVisible = action.payload; 21 | }, 22 | setConnectNostrModalVisible(state, action) { 23 | state.connectNostrModalVisible = action.payload; 24 | }, 25 | setTurnOnNostrDrawerVisible(state, action) { 26 | state.turnOnNostrDrawerVisible = action.payload; 27 | }, 28 | setOnlyMobileSupportedVisible(state, action) { 29 | state.onlyMobileSupportedVisible = action.payload; 30 | } 31 | } 32 | }); 33 | export const { 34 | setNostrModalVisible, 35 | setWalletConnectModalVisible, 36 | setLanguageModalVisible, 37 | setConnectNostrModalVisible, 38 | setTurnOnNostrDrawerVisible, 39 | setOnlyMobileSupportedVisible 40 | } = modalSlice.actions; 41 | export default modalSlice.reducer; 42 | -------------------------------------------------------------------------------- /src/config/icons.js: -------------------------------------------------------------------------------- 1 | import { 2 | ARBITRUM, 3 | ARBITRUM_TESTNET, 4 | AVALANCHE, 5 | AVALANCHE_FUJI, 6 | GOERLI, 7 | ETH_MAINNET, 8 | MAINNET, 9 | STACKS 10 | } from "config/chains"; 11 | import arbitrum from "img/ic_arbitrum_24.svg"; 12 | import avalanche from "img/ic_avalanche_24.svg"; 13 | import avalancheTestnet from "img/ic_avalanche_testnet_24.svg"; 14 | import goerli from "img/ic_goerli_light.svg"; 15 | import unknown from "img/unknown-logo.png"; 16 | import ethereum from "img/rsz_ethereum.jpg"; 17 | import binance from "img/rsz_binance.jpg"; 18 | 19 | // import eth from "img/ic_goerli_light.svg"; 20 | const ICONS = { 21 | [ARBITRUM]: { 22 | network: arbitrum 23 | }, 24 | [AVALANCHE]: { 25 | network: avalanche 26 | }, 27 | [GOERLI]: { 28 | network: goerli 29 | }, 30 | [ETH_MAINNET]: { 31 | network: ethereum 32 | }, 33 | [MAINNET]: { 34 | network: binance 35 | }, 36 | [STACKS]: { 37 | network: unknown 38 | }, 39 | [ARBITRUM_TESTNET]: { 40 | network: arbitrum 41 | }, 42 | [AVALANCHE_FUJI]: { 43 | network: avalancheTestnet 44 | } 45 | }; 46 | 47 | export function getIcon(chainId, label) { 48 | if (chainId in ICONS) { 49 | if (label in ICONS[chainId]) { 50 | return ICONS[chainId][label] || unknown; 51 | } 52 | } 53 | } 54 | export function getIcons(chainId) { 55 | if (!chainId) return; 56 | if (chainId in ICONS) { 57 | return ICONS[chainId] || unknown; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/pages/Deposit/comps/DepositHelpModal.jsx: -------------------------------------------------------------------------------- 1 | import BaseModal from "components/Common/Modal/Modal"; 2 | import { useCallback, useEffect, useRef } from "react"; 3 | import DepositDescription from "./DepositDescription"; 4 | import { Button } from "antd"; 5 | import * as Lockr from "lockr"; 6 | import "./DepositHelpModal.scss"; 7 | export default function DepositHelpModal() { 8 | const modalRef = useRef(null); 9 | const onCancel = useCallback(() => { 10 | Lockr.set("hasShowDepositHelpModal", true); 11 | modalRef.current.handleOk(); 12 | }, []); 13 | useEffect(() => { 14 | if (!Lockr.get("hasShowDepositHelpModal")) { 15 | modalRef.current.showModal(); 16 | } 17 | }, []); 18 | return ( 19 | 20 |
21 |

22 | Before trading on NostrAssets, need deposit your tokens from ERC20 or 23 | BRC20 wallet to your Nostr account. 24 |

25 | 26 |
27 | 28 |
29 | 38 |
39 |
40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /src/components/Common/SEO.jsx: -------------------------------------------------------------------------------- 1 | import { Helmet } from "react-helmet"; 2 | 3 | function SEO(props) { 4 | const { children, ...customMeta } = props; 5 | const meta = { 6 | description: 7 | "NostrAssets is the first fully decentralized exchange built on Nostr Protocol, powering zero-network fees, super-fast transactions, and offers one of the lowest trading fees in the market. Use ERC20 USDT to trade BRC20 tokens on NostrAssets today!", 8 | type: "exchange", 9 | ...customMeta 10 | }; 11 | return ( 12 | <> 13 | 14 | {meta.title} 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | {/* */} 25 | 26 | 27 | 28 | 29 | {children} 30 | 31 | ); 32 | } 33 | 34 | export default SEO; 35 | -------------------------------------------------------------------------------- /src/lib/downloadImage.js: -------------------------------------------------------------------------------- 1 | export default async function downloadImage(dataURI, filename) { 2 | const blob = await (await fetch(dataURI)).blob(); 3 | if (typeof window.navigator.msSaveBlob !== "undefined") { 4 | // IE doesn't allow using a blob object directly as link href. 5 | // Workaround for "HTML7007: One or more blob URLs were 6 | // revoked by closing the blob for which they were created. 7 | // These URLs will no longer resolve as the data backing 8 | // the URL has been freed." 9 | window.navigator.msSaveBlob(blob, filename); 10 | return; 11 | } 12 | // Other browsers 13 | // Create a link pointing to the ObjectURL containing the blob 14 | const blobURL = window.URL.createObjectURL(blob); 15 | const tempLink = document.createElement("a"); 16 | tempLink.style.display = "none"; 17 | tempLink.href = blobURL; 18 | tempLink.setAttribute("download", filename); 19 | // Safari thinks _blank anchor are pop ups. We only want to set _blank 20 | // target if the browser does not support the HTML5 download attribute. 21 | // This allows you to download files in desktop safari if pop up blocking 22 | // is enabled. 23 | if (typeof tempLink.download === "undefined") { 24 | tempLink.setAttribute("target", "_blank"); 25 | } 26 | document.body.appendChild(tempLink); 27 | tempLink.click(); 28 | document.body.removeChild(tempLink); 29 | setTimeout(() => { 30 | // For Firefox it is necessary to delay revoking the ObjectURL 31 | window.URL.revokeObjectURL(blobURL); 32 | }, 100); 33 | } 34 | -------------------------------------------------------------------------------- /src/img/Transfer.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/NetworkDropdown/LanguageModalContent.js: -------------------------------------------------------------------------------- 1 | import { dynamicActivate, defaultLocale, locales } from "lib/i18n"; 2 | import { importImage } from "lib/legacy"; 3 | import cx from "classnames"; 4 | import { LANGUAGE_LOCALSTORAGE_KEY } from "config/localStorage"; 5 | import checkedIcon from "img/ic_checked.svg"; 6 | import { useRef } from "react"; 7 | import { setLanguageModalVisible } from "store/reducer/modalReducer"; 8 | import { useDispatch } from "react-redux"; 9 | export default function LanguageModalContent() { 10 | const dispatch = useDispatch(); 11 | const currentLanguage = useRef(localStorage.getItem(LANGUAGE_LOCALSTORAGE_KEY) || defaultLocale); 12 | return Object.keys(locales).map((item) => { 13 | const image = importImage(`flag_${item}.svg`); 14 | return ( 15 |
{ 21 | await dynamicActivate(item); 22 | dispatch(setLanguageModalVisible(false)); 23 | }} 24 | > 25 |
26 |
27 | {{locales[item]}} 28 |
29 | {locales[item]} 30 |
31 |
32 | {currentLanguage.current === item && {locales[item]}} 33 |
34 |
35 | ); 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /src/lib/utils/math.js: -------------------------------------------------------------------------------- 1 | function add(arg1, arg2) { 2 | let r1; 3 | let r2; 4 | let m = 0; 5 | try { 6 | r1 = arg1.toString().split(".")[1].length; 7 | } catch (e) { 8 | r1 = 0; 9 | } 10 | try { 11 | r2 = arg2.toString().split(".")[1].length; 12 | } catch (e) { 13 | r2 = 0; 14 | } 15 | m = 10 ** Math.max(r1, r2); 16 | return (arg1 * m + arg2 * m) / m; 17 | } 18 | 19 | function cut(arg1, arg2) { 20 | let r1; 21 | let r2; 22 | let m = 0; 23 | let n = 0; 24 | try { 25 | r1 = arg1.toString().split(".")[1].length; 26 | } catch (e) { 27 | r1 = 0; 28 | } 29 | try { 30 | r2 = arg2.toString().split(".")[1].length; 31 | } catch (e) { 32 | r2 = 0; 33 | } 34 | m = 10 ** Math.max(r1, r2); 35 | n = r1 >= r2 ? r1 : r2; 36 | return ((arg1 * m - arg2 * m) / m).toFixed(n); 37 | } 38 | 39 | function nul(arg1, arg2) { 40 | let m = 0; 41 | const s1 = arg1.toString(); 42 | const s2 = arg2.toString(); 43 | try { 44 | m += s1.split(".")[1].length; 45 | } catch (e) { 46 | } 47 | try { 48 | m += s2.split(".")[1].length; 49 | } catch (e) { 50 | } 51 | return (Number(s1.replace(".", "")) * Number(s2.replace(".", ""))) / 10 ** m; 52 | } 53 | 54 | function division(arg1, arg2) { 55 | let t1 = 0; 56 | let t2 = 0; 57 | let r1 = 0; 58 | let r2 = 0; 59 | try { 60 | t1 = arg1.toString().split(".")[1].length; 61 | } catch (e) { 62 | } 63 | try { 64 | t2 = arg2.toString().split(".")[1].length; 65 | } catch (e) { 66 | } 67 | r1 = Number(arg1.toString().replace(".", "")); 68 | r2 = Number(arg2.toString().replace(".", "")); 69 | return (r1 / r2) * 10 ** (t2 - t1); 70 | } 71 | 72 | export { add, cut, nul, division }; 73 | -------------------------------------------------------------------------------- /src/img/walletconnect-circle-blue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/NetworkDropdown/LanguageModalContent.jsx: -------------------------------------------------------------------------------- 1 | import { dynamicActivate, isTestLanguage, locales } from "lib/i18n"; 2 | import { importImage } from "lib/legacy"; 3 | import cx from "classnames"; 4 | import { LANGUAGE_LOCALSTORAGE_KEY } from "config/localStorage"; 5 | import checkedIcon from "img/ic_checked.svg"; 6 | 7 | /* type Props = { 8 | currentLanguage: { 9 | current: string | undefined; 10 | }; 11 | }; */ 12 | 13 | export default function LanguageModalContent({ currentLanguage }) { 14 | return ( 15 | <> 16 | {Object.keys(locales).map((item) => { 17 | const image = importImage(`flag_${item}.svg`); 18 | return ( 19 |
{ 25 | if (!isTestLanguage(item)) { 26 | localStorage.setItem(LANGUAGE_LOCALSTORAGE_KEY, item); 27 | } 28 | dynamicActivate(item); 29 | }} 30 | > 31 |
32 |
33 | {isTestLanguage(item) ? "🫐" : {locales[item]}} 34 |
35 | {locales[item]} 36 |
37 |
38 | {currentLanguage.current === item && {locales[item]}} 39 |
40 |
41 | ); 42 | })} 43 | 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /src/components/Modals/SetupNetWorkModal.jsx: -------------------------------------------------------------------------------- 1 | import { useRef, useEffect, useCallback } from "react"; 2 | import { useNetwork, useSwitchNetwork } from "wagmi"; 3 | import BaseModal from "components/Common/Modal/Modal"; 4 | import { isInTokenPocket } from "lib/utils/userAgent"; 5 | import "./index.scss"; 6 | import { Button } from "antd"; 7 | export default function SetupNetWorkModal() { 8 | const { chain, chains } = useNetwork(); 9 | const { 10 | error, 11 | isLoading, 12 | pendingChainId, 13 | switchNetwork, 14 | } = useSwitchNetwork(); 15 | 16 | const modalRef = useRef(null); 17 | const handleSwitch = useCallback(() => { 18 | switchNetwork(chains[0].id); 19 | if (isInTokenPocket()) { 20 | modalRef.current.handleCancel(); 21 | } 22 | }, [chains, switchNetwork]); 23 | useEffect(() => { 24 | if ( 25 | chain?.unsupported && 26 | window?.ethereum?.networkVersion != chains[0].id 27 | ) { 28 | modalRef.current.showModal(); 29 | } else { 30 | modalRef.current.handleCancel(); 31 | } 32 | }, [chain, chain?.unsupported, chains]); 33 | return ( 34 | <> 35 | 42 |
43 |

44 | {`Deposit only supported on ${chains[0]?.name} Network at the moment.`} 45 |

46 | 54 |
55 |
56 | 57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /src/pages/Deposit/index.scss: -------------------------------------------------------------------------------- 1 | .deposit-container { 2 | flex: 1; 3 | padding: 20px 20px 60px; 4 | .deposit-content { 5 | margin-top: 20px; 6 | border: 1px solid var(--color-border-grey); 7 | border-radius: 20px; 8 | padding-bottom: 20px; 9 | } 10 | .deposit-title { 11 | margin-top: 40px; 12 | padding-left: 20px; 13 | text-align: left; 14 | font-size: 18px; 15 | font-weight: bold; 16 | &__value { 17 | padding-left: 20px; 18 | cursor: pointer; 19 | } 20 | } 21 | .deposit-invoices { 22 | margin: 0 auto 20px; 23 | width: 660px; 24 | &-time { 25 | font-size: 14px; 26 | padding-right: 10px; 27 | color: rgba(255, 255, 255, 0.85); 28 | } 29 | &-qrcode { 30 | position: relative; 31 | padding-top: 20px; 32 | } 33 | } 34 | .deposit-send-btn { 35 | width: 120px; 36 | } 37 | @media (max-width: 768px) { 38 | .deposit-invoices { 39 | width: 100%; 40 | } 41 | } 42 | } 43 | 44 | .step-container { 45 | width: 400px; 46 | margin: 0 auto; 47 | margin-top: 30px; 48 | } 49 | @media screen and (max-width: 768px) { 50 | .step-container { 51 | width: 100%; 52 | margin: 0 auto; 53 | margin-top: 30px; 54 | } 55 | .deposit-form .deposit-balance { 56 | padding-left: 0; 57 | } 58 | .deposit-banner-box { 59 | display: none; 60 | } 61 | .deposit-container { 62 | .deposit-content { 63 | margin-top: 0; 64 | border: none; 65 | } 66 | .deposit-title { 67 | margin-top: 0; 68 | padding-left: 0; 69 | text-align: left; 70 | font-size: 18px; 71 | font-weight: bold; 72 | &__value { 73 | padding-left: 20px; 74 | cursor: pointer; 75 | } 76 | } 77 | // .banner { 78 | // display: none; 79 | // } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/pages/Explore/explore.scss: -------------------------------------------------------------------------------- 1 | .market-explore { 2 | // padding:0px 40px 0px; 3 | height:100%; 4 | display: flex; 5 | flex-direction: column; 6 | &-title { 7 | font-size: 24px; 8 | padding-top:10px; 9 | &__intro { 10 | font-size:14px; 11 | color:var(--color-text-base); 12 | } 13 | } 14 | &-filters { 15 | padding:20px; 16 | margin-top:20px; 17 | background-color: var(--color-layer-base); 18 | border-radius: 10px; 19 | .filter-text { 20 | font-size:18px; 21 | } 22 | &-items { 23 | display: flex; 24 | flex-wrap: wrap; 25 | } 26 | &-item{ 27 | padding:10px 0; 28 | margin-right:10px; 29 | &__date { 30 | width:auto; 31 | } 32 | &__range { 33 | width:200px; 34 | } 35 | &__select { 36 | width:auto; 37 | 38 | label { 39 | padding-right:5px; 40 | } 41 | .select { 42 | width:120px; 43 | } 44 | } 45 | &__input { 46 | 47 | label { 48 | padding-right: 5px; 49 | } 50 | .input { 51 | width:160px; 52 | &.address { 53 | width:260px; 54 | } 55 | } 56 | } 57 | } 58 | 59 | } 60 | .explore-table { 61 | margin-top:20px; 62 | background-color: var(--color-layer-base); 63 | margin-bottom: 20px; 64 | border-radius: 10px; 65 | padding: 0 20px; 66 | } 67 | .explore-more { 68 | padding-left:10px; 69 | cursor: pointer; 70 | } 71 | .detail{ 72 | color: #00FFFF; 73 | cursor: pointer; 74 | } 75 | } 76 | @media screen and (max-width: 768px) { 77 | .market-explore{ 78 | &-filters{ 79 | padding: 0 10px 0; 80 | margin-top:10px; 81 | } 82 | .explore-table{ 83 | padding: 0 10px; 84 | margin-top:10px; 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /src/pages/Testnet/comps/TokenTip.jsx: -------------------------------------------------------------------------------- 1 | import { Tooltip } from "antd"; 2 | import { useMemo } from "react"; 3 | import { useSelector } from "react-redux"; 4 | import { ExclamationCircleOutlined } from "@ant-design/icons"; 5 | export default function TokenTip() { 6 | // const { tokenList } = useSelector(({ market }) => market); 7 | const receiveTokens = useMemo(() => { 8 | // return ["ORDI", 9 | // "OXBT", 10 | // "VMPX", 11 | // "BTOC", 12 | // "MXRC", 13 | // "ZBIT", 14 | // "PEPE", 15 | // "MEME", 16 | // "DOGE", 17 | // "SHIB"] 18 | return [ 19 | "ORDI", 20 | "OXBT", 21 | "VMPX", 22 | "BTOC", 23 | "MXRC", 24 | "ZBIT", 25 | "PEPE", 26 | "MEME", 27 | "SATS", 28 | "BANK", 29 | "$ORE", 30 | // "LVDI" 31 | ] 32 | }, []) 33 | const pionerPointsTitle = useMemo( 34 | () => ( 35 |
36 |

37 | By holding the following BRC20 tokens, you can receive bonus pioneer 38 | points. Each token type held, you will earn an extra 10 points, with a 39 | maximum of 100 bonus points. To claim the additional points, please 40 | connect using your Unisat Wallet, which will allow us to verify your 41 | token ownership. 42 |

43 |
    44 | {receiveTokens.map((token) => ( 45 |
  • 46 | {token} 47 |
  • 48 | ))} 49 |
50 |
51 | ), 52 | [receiveTokens] 53 | ); 54 | return ( 55 | <> 56 | 61 | 62 | 63 | 64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /src/config/wagmiConfig.js: -------------------------------------------------------------------------------- 1 | import { createConfig, configureChains } from "wagmi"; 2 | import { mainnet, goerli, arbitrum, bsc, optimism } from "wagmi/chains"; 3 | import { publicProvider } from "wagmi/providers/public"; 4 | import { jsonRpcProvider } from "wagmi/providers/jsonRpc"; 5 | import { CoinbaseWalletConnector } from "wagmi/connectors/coinbaseWallet"; 6 | import { InjectedConnector } from "wagmi/connectors/injected"; 7 | import { MetaMaskConnector } from "wagmi/connectors/metaMask"; 8 | import { WalletConnectConnector } from "wagmi/connectors/walletConnect"; 9 | import { alchemyProvider } from "wagmi/providers/alchemy"; 10 | const arrJsonRpcUri = { 11 | [mainnet.id]: "https://eth.llamarpc.com" 12 | }; 13 | // [mainnet, goerli, arbitrum, bsc, optimism] 14 | const supportChains = (process.env.REACT_APP_CURRENT_ENV === "dev" || process.env.REACT_APP_CURRENT_ENV === 'test') ? [goerli] : [mainnet]; 15 | const { chains, publicClient, webSocketPublicClient } = configureChains(supportChains, [ 16 | alchemyProvider({ apiKey: "0QZmS8gya0KlkUP09WcwEPj98SjPxMtw" }), 17 | publicProvider(), 18 | jsonRpcProvider({ 19 | rpc: (chain) => { 20 | return { 21 | http: arrJsonRpcUri[chain.id] 22 | }; 23 | } 24 | }) 25 | ]); 26 | 27 | const connectorItems = [ 28 | new MetaMaskConnector({ chains }), 29 | new CoinbaseWalletConnector({ 30 | chains, 31 | options: { 32 | appName: "nostr" 33 | } 34 | }), 35 | new InjectedConnector({ 36 | chains, 37 | options: { 38 | name: "Injected", 39 | shimDisconnect: true 40 | } 41 | }), 42 | new WalletConnectConnector({ 43 | chains, 44 | options: { 45 | projectId: "2dbfdce8f774975e4c47ca92870dba88" 46 | } 47 | }) 48 | ]; 49 | 50 | const config = createConfig({ 51 | autoConnect: true, 52 | connectors: connectorItems, 53 | publicClient, 54 | webSocketPublicClient 55 | }); 56 | export default config; 57 | -------------------------------------------------------------------------------- /src/pages/Marketplace/comps/Market/index.scss: -------------------------------------------------------------------------------- 1 | .ant-modal-body{ 2 | width: 100%; 3 | } 4 | .market-buy-list{ 5 | width: 400px; 6 | max-width: 100%; 7 | padding: 0 20px; 8 | .market-buy-item{ 9 | display: flex; 10 | justify-content: space-between; 11 | margin-top: 10px; 12 | .market-buy-label{ 13 | width: 120px; 14 | color: var(--color-text-dark); 15 | } 16 | .market-buy-value{ 17 | flex: 1; 18 | text-align: left; 19 | } 20 | 21 | } 22 | .market-buy-available{ 23 | margin-top: 30px; 24 | color: var(--color-text-dark); 25 | font-size: 12px; 26 | text-align: center; 27 | } 28 | .market-buy-submit{ 29 | margin-top: 10px; 30 | margin-bottom: 15px; 31 | text-align: center; 32 | .listing-submit-btn{ 33 | min-width: 180px; 34 | } 35 | } 36 | } 37 | .ant-modal-header,.ant-modal-title { 38 | width:100%; 39 | } 40 | .ant-modal-header { 41 | border-bottom:1px solid var(--color-border-lighter); 42 | } 43 | .market-form-title { 44 | padding-bottom:10px; 45 | .market-form-title-tag { 46 | margin-right: 10px; 47 | background-color: var(--color-layer-lighter); 48 | border-radius: 4px; 49 | padding:2px 10px; 50 | &__sell{ 51 | color:var(--color-orange); 52 | } 53 | &__buy{ 54 | color:var(--color-green); 55 | } 56 | } 57 | } 58 | // @media screen and (max-width: 768px) { 59 | // .listing-form { 60 | // width:100%; 61 | // .ant-form-item .ant-form-item-label, .ant-form-item .ant-form-item-control{ 62 | // flex: initial; 63 | // } 64 | // .ant-form-item .ant-form-item-label >label{ 65 | // width:90px; 66 | // font-size: 12px; 67 | // } 68 | // .ant-form-item{ 69 | // margin-bottom: 6px; 70 | // } 71 | // .listing-input { 72 | // width:120px; 73 | // } 74 | // .listing-select{ 75 | // width:160px; 76 | // } 77 | // } 78 | // } -------------------------------------------------------------------------------- /src/components/Common/Modal/Modal.css: -------------------------------------------------------------------------------- 1 | .Modal { 2 | position: fixed; 3 | z-index: 1001; 4 | top: 0; 5 | left: 0; 6 | right: 0; 7 | bottom: 0; 8 | display: flex; 9 | align-items: center; 10 | justify-content: center; 11 | text-align: left; 12 | } 13 | 14 | .Modal-scrollable .Modal-content .Modal-body { 15 | overscroll-behavior: smooth; 16 | max-height: calc(80vh - 5.5rem); 17 | overflow-y: auto; 18 | padding-right: 5px; 19 | } 20 | 21 | .Modal-backdrop { 22 | position: fixed; 23 | z-index: 10; 24 | top: 0; 25 | bottom: 0; 26 | left: 0; 27 | right: 0; 28 | background: rgba(0, 0, 0, 0.9); 29 | } 30 | 31 | .Modal-content { 32 | z-index: 20; 33 | position: relative; 34 | max-width: 100%; 35 | max-height: 90vh; 36 | overflow: auto; 37 | background: #16182e; 38 | border-radius: 4px; 39 | } 40 | 41 | .divider { 42 | border-bottom: 1px solid #ffffff29; 43 | margin-bottom: 1.5rem; 44 | } 45 | 46 | .Modal.non-scrollable .Modal-content { 47 | overflow: visible; 48 | } 49 | 50 | .Modal-title-bar { 51 | display: flex; 52 | justify-content: space-between; 53 | align-items: center; 54 | margin: 1.5rem; 55 | } 56 | .Modal-body { 57 | margin: 1.5rem; 58 | } 59 | .Modal-body::-webkit-scrollbar { 60 | width: 0.6rem; 61 | } 62 | 63 | .Modal-body::-webkit-scrollbar-track { 64 | background-color: #1c1c1c; 65 | border-radius: 155rem; 66 | } 67 | 68 | .Modal-body::-webkit-scrollbar-thumb { 69 | background-color: #949393; 70 | border-radius: 155rem; 71 | } 72 | 73 | .Modal-title { 74 | text-align: left; 75 | font-size: var(--font-md); 76 | line-height: 1; 77 | } 78 | 79 | .Modal-close-button { 80 | text-align: right; 81 | } 82 | 83 | .Modal-close-icon { 84 | opacity: 0.6; 85 | cursor: pointer; 86 | text-align: right; 87 | display: inline-block; 88 | } 89 | 90 | .Modal-close-icon:hover { 91 | opacity: 0.9; 92 | } 93 | 94 | .Modal-note { 95 | margin-bottom: 1.5rem; 96 | margin-top: 0.8rem; 97 | } 98 | -------------------------------------------------------------------------------- /src/img/Asset.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/hooks/useAccountInit.js: -------------------------------------------------------------------------------- 1 | import { useSelector, useDispatch } from "react-redux"; 2 | import { setWalletConnectModalVisible } from "store/reducer/modalReducer"; 3 | import { useState, useEffect } from "react"; 4 | import { useAccount, useNetwork } from "wagmi"; 5 | import { initNostrAccount } from "store/reducer/userReducer"; 6 | import { setAccount, setChainId, setActive } from "store/reducer/userReducer"; 7 | import { isInTokenPocket } from "lib/utils/userAgent"; 8 | export default function useAccountInit() { 9 | const { address, connector, isConnected } = useAccount(); 10 | const { nostrAccount, account } = useSelector(({ user }) => user); 11 | const { chain } = useNetwork(); 12 | const connectPlat = useSelector(({ user }) => user.connectPlat); 13 | const walletConnectModalVisible = useSelector(({ modal }) => modal.walletConnectModalVisible); 14 | const dispatch = useDispatch(); 15 | useEffect(() => { 16 | if (connectPlat === "ETH") { 17 | dispatch(setAccount(address)); 18 | dispatch(setChainId(chain?.id)); 19 | dispatch(setActive(isConnected)); 20 | if (address && isConnected) { 21 | if (walletConnectModalVisible) { 22 | dispatch(setWalletConnectModalVisible(false)); 23 | } 24 | } 25 | } 26 | if (connectPlat === "BTC") { 27 | dispatch(setActive(!!account)); 28 | if (account) { 29 | if (walletConnectModalVisible) { 30 | dispatch(setWalletConnectModalVisible(false)); 31 | } 32 | } 33 | } 34 | }, [account, address, chain?.id, connectPlat, connector, dispatch, isConnected, nostrAccount, walletConnectModalVisible]); 35 | useEffect(() => { 36 | const getKey = async () => { 37 | if (!nostrAccount) { 38 | const albyNostrAccount = await window.nostr.getPublicKey(); 39 | dispatch(initNostrAccount(albyNostrAccount)); 40 | } 41 | }; 42 | if (window.nostr && isInTokenPocket() && !nostrAccount) { 43 | getKey().catch((err) => console.log(err)); 44 | } 45 | return () => null; 46 | }, [dispatch, nostrAccount]); 47 | return null; 48 | } 49 | -------------------------------------------------------------------------------- /src/store/reducer/basicReducer.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | import * as Lockr from "lockr"; 3 | const localStorageRelay = Lockr.get("initRelayUrlsv2"); 4 | let maplocalStorageRelay = localStorageRelay ? localStorageRelay.map(relay => ({ ...relay, status: 'disconnected' })) : null 5 | export const basicSlice = createSlice({ 6 | name: "basic", 7 | initialState: { 8 | relayUrls: maplocalStorageRelay || [ 9 | { 10 | address: "wss://relay.nostrassets.com", 11 | offical: true, 12 | link: true, 13 | delete: false, 14 | status: "disconnected" 15 | }, 16 | 17 | { 18 | address: "wss://relay.damus.io", 19 | offical: true, 20 | link: true, 21 | delete: false, 22 | status: "disconnected" 23 | } 24 | ], 25 | isRelayConnected: false 26 | }, 27 | reducers: { 28 | addRelayUrls(state, { payload }) { 29 | state.relayUrls = [...state.relayUrls, { ...payload }]; 30 | Lockr.set("initRelayUrlsv2", state.relayUrls); 31 | }, 32 | removeRelayUrls(state, { payload }) { 33 | const willRelayUrlIndex = state.relayUrls.findIndex((relayUrlItem) => relayUrlItem.address === payload.address); 34 | state.relayUrls.splice(willRelayUrlIndex, 1); 35 | Lockr.set("initRelayUrlsv2", state.relayUrls); 36 | }, 37 | initRelayUrls(state, { payload }) { 38 | state.relayUrls = payload; 39 | Lockr.set("initRelayUrlsv2", payload); 40 | }, 41 | updateRelayStatus(state, { payload }) { 42 | const itemRelay = state.relayUrls.find((item) => item.address === payload.address); 43 | itemRelay.status = payload.status; 44 | Lockr.set("initRelayUrlsv2", state.relayUrls); 45 | if (itemRelay.address === 'wss://relay.nostrassets.com') { 46 | if (itemRelay.status === 'connected') { 47 | state.isRelayConnected = true 48 | } 49 | } 50 | } 51 | } 52 | }); 53 | export const { addRelayUrls, removeRelayUrls, initRelayUrls, updateRelayStatus } = 54 | basicSlice.actions; 55 | export default basicSlice.reducer; 56 | -------------------------------------------------------------------------------- /src/img/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/components/Modals/TurnOnNostrDrawer.jsx: -------------------------------------------------------------------------------- 1 | import { Button, Drawer, Radio, Space } from "antd"; 2 | import { useState, useCallback } from "react"; 3 | import { useSelector, useDispatch } from "react-redux"; 4 | import { setTurnOnNostrDrawerVisible } from "store/reducer/modalReducer"; 5 | import { CloseOutlined } from "@ant-design/icons"; 6 | import turnon1 from "img/turnon1.png"; 7 | import turnon2 from "img/turnon2.png"; 8 | const TurnOnNostrDrawer = () => { 9 | const { turnOnNostrDrawerVisible } = useSelector(({ modal }) => modal); 10 | const dispatch = useDispatch(); 11 | const onClose = useCallback(() => { 12 | dispatch(setTurnOnNostrDrawerVisible(false)); 13 | }, [dispatch]); 14 | return ( 15 | <> 16 | {/* 17 | 18 | top 19 | right 20 | bottom 21 | left 22 | 23 | 26 | */} 27 | 36 | 39 | {/* */} 42 | 43 | } 44 | > 45 |

Click on "Mine" and open the switch for “Turn on Nostr" to activate Nostr.

46 |
47 |
48 | 49 |
50 |
51 | 52 |
53 |
54 |
55 | 56 | ); 57 | }; 58 | export default TurnOnNostrDrawer; 59 | -------------------------------------------------------------------------------- /src/store/reducer/userReducer.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | import * as Lockr from "lockr"; 3 | import { isInTokenPocket } from "lib/utils/userAgent"; 4 | import { nip19 } from "nostr-tools"; 5 | export const userSlice = createSlice({ 6 | name: "user", 7 | initialState: { 8 | connectPlat: Lockr.get("connectPlat") || "ETH", 9 | selectedTokenPlatform: "Lightning", 10 | account: "", // 11 | chainId: 1, 12 | library: null, 13 | proMode: { 14 | hasInit: false, 15 | value: false 16 | }, 17 | nostrAccount: !isInTokenPocket() ? Lockr.get("nostrAccount") : "", 18 | npubNostrAccount: !isInTokenPocket() 19 | ? Lockr.get("nostrAccount") 20 | ? nip19.npubEncode(Lockr.get("nostrAccount")) 21 | : "" 22 | : "", 23 | balanceList: {}, 24 | userInfo: {}, 25 | active: false, 26 | isBindNostrAddress: false 27 | }, 28 | reducers: { 29 | setAccount(state, action) { 30 | state.account = action.payload; 31 | }, 32 | setProMode(state, action) { 33 | state.proMode = { ...action.payload } 34 | }, 35 | setChainId(state, action) { 36 | state.chainId = action.payload; 37 | }, 38 | 39 | setActive(state, action) { 40 | state.active = action.payload; 41 | }, 42 | initNostrAccount(state, action) { 43 | state.nostrAccount = action.payload; 44 | state.npubNostrAccount = action.payload ? nip19.npubEncode(action.payload) : ""; 45 | }, 46 | setBalanceList(state, action) { 47 | state.balanceList = action.payload; 48 | }, 49 | setConnectPlat(state, action) { 50 | state.connectPlat = action.payload; 51 | }, 52 | setSelectedTokenPlatForm(state, action) { 53 | state.selectedTokenPlatform = action.payload; 54 | }, 55 | setIsBindNostrAddress(state, action) { 56 | state.isBindNostrAddress = action.payload; 57 | } 58 | } 59 | }); 60 | export const { 61 | setAccount, 62 | setChainId, 63 | setIsProMode, 64 | setProMode, 65 | setActive, 66 | initNostrAccount, 67 | setConnectPlat, 68 | setSelectedTokenPlatForm, 69 | setIsBindNostrAddress, 70 | setBalanceList 71 | } = userSlice.actions; 72 | export default userSlice.reducer; 73 | -------------------------------------------------------------------------------- /src/lib/legacy.js: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import { bigNumberify, formatAmount } from "./numbers"; 3 | 4 | export function deserialize(data) { 5 | for (const [key, value] of Object.entries(data)) { 6 | if (value._type === "BigNumber") { 7 | data[key] = bigNumberify(value.value); 8 | } 9 | } 10 | return data; 11 | } 12 | 13 | export function getLeverageStr(leverage) { 14 | if (leverage && ethers.BigNumber.isBigNumber(leverage)) { 15 | if (leverage.lt(0)) { 16 | return "> 100x"; 17 | } 18 | return `${formatAmount(leverage, 4, 2, true)}x`; 19 | } 20 | } 21 | 22 | export function shortenAddress(address, length) { 23 | if (!length) { 24 | return ""; 25 | } 26 | if (!address) { 27 | return address; 28 | } 29 | if (address.length < 10) { 30 | return address; 31 | } 32 | let left = Math.floor((length - 3) / 2) + 1; 33 | return address.substring(0, left) + "..." + address.substring(address.length - (length - (left + 3)), address.length); 34 | } 35 | 36 | export function isMobileDevice() { 37 | return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); 38 | } 39 | 40 | export const CHART_PERIODS = { 41 | "5m": 60 * 5, 42 | "15m": 60 * 15, 43 | "1h": 60 * 60, 44 | "4h": 60 * 60 * 4, 45 | "1d": 60 * 60 * 24 46 | }; 47 | 48 | export function getTotalVolumeSum(volumes) { 49 | if (!volumes || volumes.length === 0) { 50 | return; 51 | } 52 | let volume = bigNumberify(0); 53 | 54 | for (let i = 0; i < volumes.length; i++) { 55 | volume = volume.add(volumes[i].data.volume); 56 | } 57 | return volume; 58 | } 59 | 60 | export function getPageTitle(data) { 61 | return `${data} | Decentralized 62 | Perpetual Exchange`; 63 | } 64 | 65 | export function isHashZero(value) { 66 | return value === ethers.constants.HashZero; 67 | } 68 | export function isAddressZero(value) { 69 | return value === ethers.constants.AddressZero; 70 | } 71 | 72 | export function importImage(name) { 73 | let tokenImage = null; 74 | 75 | try { 76 | tokenImage = require("img/" + name); 77 | } catch (error) { 78 | // eslint-disable-next-line no-console 79 | console.error(error); 80 | } 81 | 82 | return tokenImage; 83 | } 84 | -------------------------------------------------------------------------------- /src/components/Modals/ConnectNostrOnTPModal.jsx: -------------------------------------------------------------------------------- 1 | import { Modal, Timeline, Row, Col, Button } from "antd"; 2 | import { useSelector, useDispatch } from "react-redux"; 3 | import { useCallback, useMemo, useState } from "react"; 4 | import { setConnectNostrModalVisible } from "store/reducer/modalReducer"; 5 | import ConnectWalletButton from "components/Common/ConnectWalletButton"; 6 | import IconTPWallet from "img/ico-tp.svg"; 7 | // import { t } from "@lingui/macro"; 8 | import "./index.scss"; 9 | export default function ConnectNostrOnTPModal() { 10 | const { connectNostrModalVisible } = useSelector(({ modal }) => modal); 11 | const dispatch = useDispatch(); 12 | const onCancel = useCallback(() => { 13 | dispatch(setConnectNostrModalVisible(false)); 14 | }, [dispatch]); 15 | const encodeTPParams = encodeURI( 16 | JSON.stringify({ 17 | // url: "https://dapp.mytokenpocket.vip/referendum/index.html#/", 18 | url: location.href, 19 | chain: "ETH", 20 | source: "" 21 | }) 22 | ); 23 | return ( 24 | <> 25 | {connectNostrModalVisible && ( 26 | 35 |

36 | Use TP Wallet to manage your Nostr keys, and you can log in using Nostr in TP Wallet. 37 |

38 |

39 | Note: Currently, only TP Wallet on mobile supports Nostr key management. 40 |

41 |
42 | 43 | Open in TP Wallet 44 | 45 |
46 | 47 | 52 |
53 | )} 54 | 55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /src/img/ic_coingecko_16.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/img/ic_coingecko_hover_16.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/abis/INostrSwapDeposit.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": true, 7 | "internalType": "address", 8 | "name": "sender", 9 | "type": "address" 10 | }, 11 | { 12 | "indexed": false, 13 | "internalType": "uint256", 14 | "name": "amount", 15 | "type": "uint256" 16 | }, 17 | { 18 | "indexed": false, 19 | "internalType": "string", 20 | "name": "target", 21 | "type": "string" 22 | } 23 | ], 24 | "name": "LogDeposit", 25 | "type": "event" 26 | }, 27 | { 28 | "inputs": [ 29 | { 30 | "internalType": "address", 31 | "name": "token", 32 | "type": "address" 33 | }, 34 | { 35 | "internalType": "uint256", 36 | "name": "amount", 37 | "type": "uint256" 38 | }, 39 | { 40 | "internalType": "string", 41 | "name": "target", 42 | "type": "string" 43 | } 44 | ], 45 | "name": "deposit", 46 | "outputs": [], 47 | "stateMutability": "nonpayable", 48 | "type": "function" 49 | }, 50 | { 51 | "inputs": [ 52 | { 53 | "internalType": "address", 54 | "name": "token", 55 | "type": "address" 56 | } 57 | ], 58 | "name": "getTreasuryMapping", 59 | "outputs": [ 60 | { 61 | "internalType": "address", 62 | "name": "", 63 | "type": "address" 64 | } 65 | ], 66 | "stateMutability": "view", 67 | "type": "function" 68 | }, 69 | { 70 | "inputs": [ 71 | { 72 | "internalType": "address", 73 | "name": "token", 74 | "type": "address" 75 | } 76 | ], 77 | "name": "removeTreasuryMapping", 78 | "outputs": [], 79 | "stateMutability": "nonpayable", 80 | "type": "function" 81 | }, 82 | { 83 | "inputs": [ 84 | { 85 | "internalType": "address", 86 | "name": "token", 87 | "type": "address" 88 | }, 89 | { 90 | "internalType": "address", 91 | "name": "treasury", 92 | "type": "address" 93 | } 94 | ], 95 | "name": "setTreasuryMapping", 96 | "outputs": [], 97 | "stateMutability": "nonpayable", 98 | "type": "function" 99 | } 100 | ] -------------------------------------------------------------------------------- /src/hooks/useWebln.js: -------------------------------------------------------------------------------- 1 | import { useCallback } from "react"; 2 | import { to } from 'await-to-js'; 3 | export default function useWebln() { 4 | const checkWebln = useCallback(async (webln) => { 5 | if (!webln) { 6 | throw new Error("Webln is not available.") 7 | } else { 8 | if (!webln.enabled) { 9 | const [err] = await to(webln.enable()) 10 | if (err) { 11 | throw new Error(err.message); 12 | } 13 | } 14 | return webln.enabled; 15 | } 16 | }, []) 17 | const detecWebLNProvider = useCallback(async (timeoutParam) => { 18 | const timeout = timeoutParam ?? 3000; 19 | const interval = 100; 20 | let handled = false; 21 | 22 | return new Promise((resolve) => { 23 | if (window.webln) { 24 | handleWebLN(); 25 | } else { 26 | document.addEventListener("webln:ready", handleWebLN, { once: true }); 27 | 28 | let i = 0; 29 | const checkInterval = setInterval(function () { 30 | if (window.webln || i >= timeout / interval) { 31 | handleWebLN(); 32 | clearInterval(checkInterval); 33 | } 34 | i++; 35 | }, interval); 36 | } 37 | 38 | function handleWebLN() { 39 | if (handled) { 40 | return; 41 | } 42 | handled = true; 43 | 44 | document.removeEventListener("webln:ready", handleWebLN); 45 | 46 | if (window.webln) { 47 | resolve(window.webln); 48 | } else { 49 | resolve(null); 50 | } 51 | } 52 | }); 53 | }, []) 54 | 55 | const makeInvoice = useCallback(async (amount = 0, defaultMemo = "") => { 56 | const webln = await detecWebLNProvider(); 57 | const enabled = await checkWebln(webln); 58 | if (enabled) { 59 | const invoice = await webln.makeInvoice({ 60 | amount: amount, 61 | defaultMemo 62 | }); 63 | return invoice; 64 | } 65 | return null; 66 | }, [checkWebln, detecWebLNProvider]) 67 | 68 | const sendPayment = useCallback(async (paymentRequest) => { 69 | const webln = await detecWebLNProvider(); 70 | const enabled = await checkWebln(webln); 71 | if (enabled) { 72 | const sendRet = await webln.sendPayment(paymentRequest); 73 | return sendRet; 74 | } 75 | return null; 76 | }, [checkWebln, detecWebLNProvider]) 77 | 78 | return { 79 | makeInvoice, 80 | sendPayment 81 | } 82 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |

5 | 6 | react 7 | 8 | 9 | nostrassets 10 | 11 | 12 | license 13 | 14 | 15 |

16 | # NostrAssets 17 | 18 | [NRC-20 Protocol](https://doc.nostrassets.com) is a technical standard for fungible tokens created using the Nostr Protocol. It enables the seamless transfer, storage, and usage of multi-chain enabled assets within the Nostr ecosystem. 19 | 20 | ## Table of Contents 21 | 22 | - [Installation](#installation) 23 | - [Usage](#usage) 24 | - [License](#license) 25 | 26 | ## Installation 27 | 28 | Use the package manager [yarn](https://pip.pypa.io/en/stable/) to install. 29 | 30 | ```bash 31 | yarn install 32 | 33 | yarn start 34 | ``` 35 | 36 | ## Usage 37 | ```bash 38 | http://localhost:3011 39 | ``` 40 | 41 | ## Online Demo 42 | 43 | [ Demo](https://test.nostrassets.com/) 44 | 45 | ## License 46 | MIT License 47 | 48 | Copyright (c) 2023 Luke 49 | 50 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 51 | 52 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 53 | 54 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /config-overrides.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | const { override, addBabelPlugin, useBabelRc, addWebpackPlugin, addWebpackModuleRule } = require("customize-cra"); 3 | const ProgressBarPlugin = require("progress-bar-webpack-plugin"); 4 | 5 | const rewiredMap = () => (config) => { 6 | config.devtool = config.mode === "development" ? "cheap-module-source-map" : false; 7 | config.externals = { 8 | 'nostr-tools': 'NostrTools' 9 | } 10 | if (config.mode !== "development") { 11 | config.devtool = false; 12 | /* invade(config.optimization.minimizer, "TerserPlugin", (e) => { 13 | e.options.extractComments = false; 14 | e.options.minimizer.options.compress.drop_console = true; 15 | e.options.minimizer.options.compress.drop_debugger = true; 16 | }); */ 17 | config.optimization.runtimeChunk = "single"; 18 | config.optimization.splitChunks = { 19 | chunks: "all", 20 | minChunks: 1, 21 | maxSize: 1000000, 22 | cacheGroups: { 23 | baseChunks: { 24 | name: "base.chunks", 25 | test: (module) => /react|react-dom|react-router-dom/.test(module.context), 26 | priority: 30 27 | }, 28 | libChunks: { 29 | name: "lib.chunks", 30 | test: (module) => 31 | /react-redux|redux|axios|dayjs|lodash|lockr|bignumber|classnames|buffer|lingui|EventEmitter|ahooks|immer|md5|sass|viem/.test( 32 | module.context 33 | ), 34 | priority: 20 35 | }, 36 | web3Chunks: { 37 | name: "web3.chunks", 38 | test: (module) => /wagmi|providers|units|ethersproject|ethers|graphql|urql|nostr-tools/.test(module.context), 39 | priority: 15 40 | }, 41 | uiChunks: { 42 | name: "ui.chunks", 43 | test: (module) => /antd|@ant-design\/icons|echarts|emoji-mart/.test(module.context), 44 | priority: 10 45 | }, 46 | default: { 47 | name: "common.chunks", 48 | minChunks: 2, 49 | priority: 5, 50 | reuseExistingChunk: true 51 | } 52 | } 53 | }; 54 | } 55 | return config; 56 | }; 57 | 58 | module.exports = override( 59 | // eslint-disable-next-line react-hooks/rules-of-hooks 60 | useBabelRc(), 61 | rewiredMap(), 62 | addWebpackModuleRule({ 63 | test: /\.po$/, 64 | use: { loader: "@lingui/loader" } 65 | }), 66 | addWebpackPlugin( 67 | new ProgressBarPlugin() 68 | ) 69 | ); 70 | -------------------------------------------------------------------------------- /src/pages/Testnet/PioneerPoints.jsx: -------------------------------------------------------------------------------- 1 | import Container from "components/Container"; 2 | import "./PionneerPoints.scss"; 3 | import { Tooltip } from "antd"; 4 | import UserPointerInfo from "./comps/UserPointerInfo"; 5 | import QuestList from "./comps/QuestList"; 6 | import DailyQuestList from "./comps/DailyQuestList"; 7 | import Guides from "img/guides.jpg"; 8 | import dayjs from "dayjs"; 9 | const utc = require("dayjs/plugin/utc"); 10 | dayjs.extend(utc); 11 | import { ExclamationCircleOutlined } from "@ant-design/icons"; 12 | import NotConnectContainer from "./comps/NotConnectContainer"; 13 | import { useSelector } from "react-redux"; 14 | import { getQueryVariable } from "lib/url"; 15 | import { useMemo } from "react"; 16 | 17 | export default function PioneerPoints() { 18 | const { npubNostrAccount } = useSelector(({ user }) => user); 19 | const nostrAccount = useMemo(() => { 20 | const queryNostrAddress = getQueryVariable("nostrAddress"); 21 | return queryNostrAddress ? queryNostrAddress : npubNostrAccount; 22 | }, [npubNostrAccount]); 23 | return ( 24 | 25 |
26 | 30 | Last updated: {dayjs.utc().format("HH:mm:ss")} (UTC) 31 | 32 | 33 |
34 | 35 | 36 | {!nostrAccount ? ( 37 | 38 | ) : ( 39 | <> 40 |
44 | {/* */} 45 |
46 | Tester Quest Tips & Guides 47 | 51 | Learn More 52 | 53 |
54 |
55 | 56 | 57 | 58 | )} 59 |
60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /src/pages/Deposit/comps/BRC20Fee.jsx: -------------------------------------------------------------------------------- 1 | import { Radio, Row, Col } from "antd"; 2 | import { useState, useMemo, useCallback, useEffect } from "react"; 3 | import { useGetRecommendFee } from "hooks/unisatWallet/useGetFees"; 4 | import { useDeepCompareEffect } from "ahooks"; 5 | const MAPFEE = { 6 | Slow: { feeKey: "hourFee", tips: "Abount 1 hours" }, 7 | Avg: { feeKey: "halfHourFee", tips: "Abount 30 minutes" }, 8 | Fast: { feeKey: "fastestFee", tips: "Abount 10 minutes" }, 9 | Custom: { feeKey: "custom" }, 10 | }; 11 | export default function BRC20Fee({ 12 | feeRate, 13 | setFee, 14 | setFeeRate, 15 | ready = false, 16 | }) { 17 | const { feesRecommended } = useGetRecommendFee(ready); 18 | 19 | const onChange = useCallback( 20 | ({ target: { value } }) => { 21 | setFeeRate(value); 22 | if (value === "Custom") { 23 | setFee(""); 24 | } else { 25 | if (feesRecommended) { 26 | setFee(feesRecommended[MAPFEE[value].feeKey]); 27 | } 28 | } 29 | }, 30 | [feesRecommended, setFee, setFeeRate] 31 | ); 32 | const options = useMemo(() => { 33 | return feesRecommended 34 | ? Object.keys(MAPFEE).map((itemKey) => { 35 | const value = itemKey; 36 | const fee = 37 | itemKey !== "Custom" ? feesRecommended[MAPFEE[itemKey].feeKey] : ""; 38 | const tips = itemKey === "Custom" ? "" : MAPFEE[itemKey].tips; 39 | return { 40 | label: ( 41 | 42 | 43 | {itemKey} 44 | 45 | {itemKey !== "Custom" && ( 46 | <> 47 | 48 | {fee} salt/vB 49 | 50 | 51 | 52 | {tips} 53 | 54 | 55 | )} 56 | 57 | ), 58 | value: value, 59 | }; 60 | }) 61 | : []; 62 | }, [feesRecommended]); 63 | useDeepCompareEffect(() => { 64 | if (feeRate && feesRecommended) { 65 | setFee(feesRecommended[MAPFEE[feeRate].feeKey]); 66 | } 67 | }, [feeRate, feesRecommended, setFee]); 68 | 69 | return ( 70 | <> 71 | 79 | 80 | ); 81 | } 82 | -------------------------------------------------------------------------------- /src/components/AddressDropdown/AddressDropdown.scss: -------------------------------------------------------------------------------- 1 | .menu-items:focus-visible { 2 | border: 1px solid #262638; 3 | } 4 | .address-btn { 5 | width: 140px; 6 | display: inline-flex; 7 | align-items: center; 8 | justify-content: center; 9 | border: none; 10 | /* color: white !important; */ 11 | } 12 | 13 | .nostr-address { 14 | padding-right: 5px; 15 | } 16 | 17 | .App-header-user-address:hover { 18 | /* background: #808aff14; */ 19 | color: red; 20 | } 21 | 22 | .menu-items { 23 | position: absolute; 24 | right: 0; 25 | top: 4.3rem; 26 | min-width: 15.5rem; 27 | width: 100%; 28 | transform-origin: top right; 29 | border-radius: 0.4rem; 30 | background: #16182e; 31 | border: 1px solid #32344c; 32 | list-style: none; 33 | cursor: pointer; 34 | outline: none; 35 | z-index: 1000; 36 | } 37 | .menu-item { 38 | display: flex !important; 39 | align-items: center; 40 | font-size: var(--font-base); 41 | color: #a0a3c4; 42 | padding-bottom: 1.5rem; 43 | font-size: var(--font-sm); 44 | padding: 0.85rem 0.8rem; 45 | border-radius: 0.4rem; 46 | } 47 | .menu-item:hover { 48 | background: #808aff14 !important; 49 | border-radius: 0.4rem; 50 | opacity: 1; 51 | color: #eee; 52 | } 53 | .menu-item > p { 54 | margin: 0px; 55 | padding-left: 1rem; 56 | } 57 | .menu-item > a { 58 | display: inline-flex; 59 | } 60 | .menu-copy-address { 61 | &-item { 62 | color: #eee; 63 | &__text { 64 | padding-left: 10px; 65 | } 66 | } 67 | } 68 | 69 | .user-address-dropdown-item { 70 | display: flex; 71 | justify-content: center; 72 | align-items: center; 73 | padding: 5px 0; 74 | img { 75 | width: 14px; 76 | height: 14px; 77 | } 78 | &-text { 79 | padding-left: 10px; 80 | flex: 1; 81 | margin: 0; 82 | } 83 | } 84 | .nostr-address-dropdown { 85 | width: 170px; 86 | } 87 | 88 | .nostr-address-dropdown-items { 89 | width: 180px; 90 | .ant-dropdown-menu { 91 | background-color: rgb(27, 31, 36); 92 | } 93 | .menu-account-balance-item { 94 | height: auto; 95 | &__title { 96 | font-size: 14px; 97 | &__text { 98 | padding-left: 10px; 99 | } 100 | } 101 | &__balance { 102 | .nostr-balance-list { 103 | list-style: none; 104 | padding: 5px 0px 0px 25px; 105 | } 106 | .nostr-balance-item { 107 | width: 100%; 108 | display: flex; 109 | justify-content: flex-end; 110 | &-name { 111 | width: 60px; 112 | } 113 | &-value { 114 | flex: 1; 115 | color: var(--color-green); 116 | } 117 | } 118 | } 119 | } 120 | .ant-typography-copy { 121 | margin-inline-start: 0; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/pages/Deposit/comps/DepositForm.scss: -------------------------------------------------------------------------------- 1 | .deposit-form { 2 | margin: 20px auto 0; 3 | display: flex; 4 | justify-content: center; 5 | .ant-form-item-label { 6 | text-align: left; 7 | } 8 | .network-selector-btn { 9 | width: 150px; 10 | text-align: center; 11 | max-width: 50%; 12 | } 13 | .deposit-plat { 14 | padding-left: 10px; 15 | color: var(--color-green); 16 | } 17 | .deposit-inscriptions { 18 | padding: 0 0 0 174px; 19 | margin-bottom: 20px; 20 | &-label { 21 | padding-bottom: 10px; 22 | } 23 | &-list { 24 | border: 1px solid #333; 25 | border-radius: 10px; 26 | width: 490px; 27 | min-height: 100px; 28 | max-height: 200px; 29 | overflow-y: auto; 30 | display: flex; 31 | justify-content: center; 32 | flex-wrap: wrap; 33 | .ant-card { 34 | margin: 10px; 35 | } 36 | } 37 | &-empty { 38 | margin: 10px auto; 39 | } 40 | } 41 | .deposit-balance { 42 | padding-left: 175px; 43 | padding-bottom: 20px; 44 | &-value { 45 | color: var(--color-green); 46 | &__red { 47 | color: var(--color-red); 48 | } 49 | } 50 | } 51 | &-switch__tip { 52 | display: block; 53 | font-size: 12px; 54 | color: var(--color-red); 55 | padding-left: 30px; 56 | text-align: center; 57 | } 58 | .mb20 { 59 | margin-bottom: 20px; 60 | } 61 | @media screen and (max-width: 768px) { 62 | .network-selector-btn { 63 | width: 110px; 64 | text-align: center; 65 | max-width: 50%; 66 | } 67 | .deposit-balance { 68 | padding-left: 0; 69 | } 70 | .deposit-inscriptions { 71 | padding: 0; 72 | margin-bottom: 20px; 73 | width: 100%; 74 | &-list { 75 | width: 100%; 76 | .ant-card { 77 | margin: 5px; 78 | } 79 | } 80 | } 81 | .fixed-btn { 82 | position: fixed; 83 | bottom: 0; 84 | left: 0; 85 | width: 100vw; 86 | padding: 15px; 87 | background: var(--color-layer-base); 88 | margin-bottom: 0; 89 | border-top: 1px solid var(--color-border-grey); 90 | z-index: 100; 91 | } 92 | } 93 | } 94 | .deposit-brc20-fees { 95 | display: flex; 96 | width: 340px; 97 | height: 100px; 98 | .ant-radio-button-wrapper { 99 | height: auto; 100 | padding: 5px; 101 | flex: 1; 102 | 103 | .fee-title { 104 | font-size: 16px; 105 | font-weight: bold; 106 | display: flex; 107 | justify-content: center; 108 | align-items: center; 109 | } 110 | .fee-tip { 111 | font-size: 12px; 112 | color: var(--color-text-dark); 113 | line-height: 100%; 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/components/Header/AppHeaderLinks.jsx: -------------------------------------------------------------------------------- 1 | import { FiX } from "react-icons/fi"; 2 | import { Trans } from "@lingui/macro"; 3 | import { Link } from "react-router-dom"; 4 | import { HeaderLink } from "./HeaderLink"; 5 | import "./Header.scss"; 6 | import RelayList from "../RelayList"; 7 | import logoImg from "img/logo_nostr.png"; 8 | import OutLinks from "../OutLinks/index"; 9 | export function AppHeaderLinks({ small, clickCloseIcon }) { 10 | const stopProp = (e) => { 11 | e.stopPropagation(); 12 | e.nativeEvent.stopImmediatePropagation(); 13 | }; 14 | 15 | return ( 16 |
clickCloseIcon && clickCloseIcon()} 19 | > 20 | {small && ( 21 |
22 | 23 | NostrAssets Logo 24 | 25 |
clickCloseIcon && clickCloseIcon()} 28 | > 29 | 30 |
31 |
32 | )} 33 |
34 | 35 | Assets 36 | 37 |
38 |
39 | 40 | Explorer 41 | 42 |
43 |
44 | 45 | Marketplace 46 | 47 |
48 | {/* */} 56 | 57 |
58 | 59 | Faucet 60 | 61 |
62 | {/* */} 70 | 71 | {small && ( 72 |
73 | 74 |
75 | )} 76 | {small && ( 77 |
86 | 87 |
88 | )} 89 |
90 | ); 91 | } 92 | -------------------------------------------------------------------------------- /src/App/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, memo, useMemo } from "react"; 2 | 3 | import wagmiConfig from "config/wagmiConfig"; 4 | import { WagmiConfig } from "wagmi"; 5 | import useScrollToTop from "lib/useScrollToTop"; 6 | import { HashRouter as Router } from "react-router-dom"; 7 | import { NostrProvider } from "lib/nostr-react"; 8 | import { Buffer } from "buffer"; 9 | Buffer.from("anything", "base64"); 10 | window.Buffer = Buffer; 11 | import "./App.scss"; 12 | import "antd/dist/reset.css"; 13 | import Routes from "./Routes"; 14 | import SEO from "components/Common/SEO"; 15 | import { i18n } from "@lingui/core"; 16 | import { I18nProvider } from "@lingui/react"; 17 | import { defaultLocale, dynamicActivate } from "lib/i18n"; 18 | import { LANGUAGE_LOCALSTORAGE_KEY } from "config/localStorage"; 19 | import useAccountInit from "hooks/useAccountInit"; 20 | import { Provider as GraphProvider } from "urql"; 21 | import { client } from "config/graphqlClient"; 22 | import { notification, message } from "antd"; 23 | import { useSelector } from "react-redux"; 24 | import { useGlobalNostrAssetsEvent } from "hooks/useNostr"; 25 | if ("ethereum" in window) { 26 | window.ethereum.autoRefreshOnNetworkChange = false; 27 | } 28 | 29 | const GlobalHooks = () => { 30 | useAccountInit(); 31 | useGlobalNostrAssetsEvent(); 32 | return null; 33 | }; 34 | const GlobalModalInit = () => { 35 | const [api, contextHolder] = notification.useNotification(); 36 | const [messageApi, messageContextHolder] = message.useMessage(); 37 | useEffect(() => { 38 | if (api) { 39 | window._notification = api; 40 | } 41 | if (messageApi) { 42 | window._message = messageApi; 43 | } 44 | }, [api, messageApi]); 45 | return ( 46 | <> 47 | {contextHolder} 48 | {messageContextHolder} 49 | 50 | ); 51 | }; 52 | function App() { 53 | useScrollToTop(); 54 | useEffect(() => { 55 | const defaultLanguage = 56 | localStorage.getItem(LANGUAGE_LOCALSTORAGE_KEY) || defaultLocale; 57 | dynamicActivate(defaultLanguage); 58 | }, []); 59 | 60 | const relayUrls = useSelector(({ basic }) => basic.relayUrls); 61 | const nostrProviderRelayUrls = useMemo(() => { 62 | return relayUrls 63 | .filter((relayUrl) => relayUrl.link === true) 64 | .map((relayUrl) => relayUrl.address); 65 | }, [relayUrls]); 66 | return ( 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | ); 83 | } 84 | 85 | export default App; 86 | -------------------------------------------------------------------------------- /src/img/wallet-connect-text.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pages/Marketplace/comps/Listing/index.scss: -------------------------------------------------------------------------------- 1 | .sell-button-checked { 2 | .ant-radio-button-checked { 3 | background: var(--color-orange); 4 | border-color: var(--color-orange); 5 | } 6 | .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled) { 7 | background: var(--color-orange); 8 | border-color: var(--color-orange); 9 | 10 | } 11 | .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover { 12 | background: var(--color-orange); 13 | border-color: var(--color-orange); 14 | } 15 | .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled)::before { 16 | background: var(--color-orange); 17 | } 18 | 19 | } 20 | .select-token-name { 21 | font-size: 14px; 22 | } 23 | .select-token-balance { 24 | font-size:12px; 25 | color:var(--color-text-dark); 26 | padding-left:5px; 27 | } 28 | .listing-form { 29 | width:440px; 30 | padding:20px 0px 0; 31 | max-width: 100%; 32 | .listing-value { 33 | padding-left:60px; 34 | } 35 | .ant-form-item .ant-form-item-label{ 36 | text-align: start; 37 | } 38 | .limit-buy-available{ 39 | font-size: 12px; 40 | color: var(--color-text-dark); 41 | text-align: center; 42 | // margin-top: 15px; 43 | } 44 | .listing-submit-btn { 45 | min-width:180px; 46 | &__sell { 47 | background-color:var(--color-orange); 48 | &:hover { 49 | background-color:var(--color-orange); 50 | } 51 | } 52 | } 53 | &-usdt { 54 | padding-left:10px; 55 | } 56 | .listing-input { 57 | width: 180px; 58 | height:32px; 59 | } 60 | .listing-select{ 61 | width:220px; 62 | } 63 | .suffix-btn { 64 | width:40px; 65 | font-size:12px; 66 | } 67 | 68 | .listing-form-balance { 69 | position:absolute; 70 | top:-30px; 71 | left:-45px; 72 | width:100%; 73 | display: flex; 74 | color:var(--color-text-dark); 75 | text-align: center; 76 | font-size:12px; 77 | &-container { 78 | width:100%; 79 | padding-right:20px; 80 | 81 | } 82 | } 83 | &-total-value { 84 | font-size: 16px; 85 | span{ 86 | font-size: 12px; 87 | } 88 | } 89 | // .listing-form-total-stats { 90 | // margin-bottom:0px; 91 | // } 92 | 93 | } 94 | @media screen and (max-width: 768px) { 95 | .listing-form { 96 | width:100%; 97 | .ant-form-item .ant-form-item-label{ 98 | flex: initial; 99 | } 100 | .ant-form-item .ant-form-item-control{ 101 | flex: 1; 102 | } 103 | .ant-form-item .ant-form-item-label >label{ 104 | width:90px; 105 | font-size: 12px; 106 | } 107 | .ant-form-item{ 108 | margin-bottom: 6px; 109 | } 110 | .listing-input { 111 | width: calc(100% - 45px); 112 | } 113 | .listing-select{ 114 | width: 100%; 115 | } 116 | } 117 | .select-token-name { 118 | font-size: 12px; 119 | } 120 | .select-token-balance { 121 | font-size:10px; 122 | } 123 | } -------------------------------------------------------------------------------- /src/App/Routes.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useCallback, lazy, Suspense, memo, useMemo } from "react"; 2 | 3 | import PageNotFound from "pages/PageNotFound/PageNotFound"; 4 | 5 | import { Header } from "components/Header/Header"; 6 | import Footer from "components/Footer/Footer"; 7 | 8 | import WalletConnectModal from "components/Modals/WalletConnectModal"; 9 | 10 | import { Switch, Route, HashRouter as Router, Redirect } from "react-router-dom"; 11 | 12 | import ConnectNostrOnTPModal from "components/Modals/ConnectNostrOnTPModal"; 13 | import ConnectNostrModal from "components/Modals/ConnectNostrModal"; 14 | import TurnOnNostrDrawer from "components/Modals/TurnOnNostrDrawer"; 15 | import OnlyMobileSupportModal from "components/Modals/OnlyMobileSupportModal"; 16 | import { Spin } from "antd"; 17 | const Explore = lazy(() => import("pages/Explore/index")); 18 | const Account = lazy(() => import("pages/Account/index")); 19 | const Deposit = lazy(() => import("pages/Deposit/index")); 20 | const Withdraw = lazy(() => import("pages/Withdraw/index")); 21 | const Transfer = lazy(() => import("pages/Transfer/index")); 22 | const Marketplace = lazy(() => import("pages/Marketplace/index")); 23 | const Faucet = lazy(() => import("pages/Testnet/ClaimTestToken")); 24 | const PioneerPoints = lazy(() => import("pages/Testnet/PioneerPoints")); 25 | 26 | function Routes({ children }) { 27 | return ( 28 | <> 29 |
30 | {children} 31 |
32 |
33 | }> 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |
72 |
73 | 74 | 75 | 76 | 77 | 78 | 79 | ); 80 | } 81 | export default memo(Routes); 82 | -------------------------------------------------------------------------------- /src/pages/Explore/comps/TokenSlider/index.js: -------------------------------------------------------------------------------- 1 | import "./index.scss"; 2 | import Slider from "react-slick"; 3 | 4 | import "slick-carousel/slick/slick.css"; 5 | import "slick-carousel/slick/slick-theme.css"; 6 | import { useCallback, useEffect, useMemo, useState } from "react"; 7 | 8 | export default function TokenSlider() { 9 | const [settings, setSettings] = useState({ 10 | dots: true, 11 | infinite: true, 12 | slidesToShow: Math.floor((document.body.clientWidth - 100) / 300) || 1, 13 | slidesToScroll: 1, 14 | autoplay: true, 15 | speed: 3000, 16 | autoplaySpeed: 3000, 17 | cssEase: "linear", 18 | nextArrow: <>, 19 | prevArrow: <> 20 | }); 21 | const handleResize = useCallback(() => { 22 | // 23 | setSettings({ 24 | dots: true, 25 | infinite: true, 26 | slidesToShow: Math.floor((document.body.clientWidth - 100) / 300) || 1, 27 | slidesToScroll: 1, 28 | autoplay: true, 29 | speed: 3000, 30 | autoplaySpeed: 3000, 31 | cssEase: "linear", 32 | nextArrow: <>, 33 | prevArrow: <> 34 | }); 35 | }, []); 36 | useEffect(() => { 37 | window.addEventListener("resize", handleResize); 38 | return () => { 39 | window.removeEventListener("resize", handleResize); 40 | }; 41 | }, [handleResize]); 42 | 43 | return ( 44 | <> 45 |
46 | 47 |
48 | ordi 1960 49 | sats≈$12.56 50 | ( 51 | +100.67% 52 | ) 53 |
54 |
55 | PEPE 1960 56 | sats≈$12.56 57 | ( 58 | +100.67% 59 | ) 60 |
61 |
62 | PUSY 1960 63 | sats≈$12.56 64 | ( 65 | +100.67% 66 | ) 67 |
68 |
69 | MEME 1960 70 | sats≈$152.56 71 | ( 72 | +100.67% 73 | ) 74 |
75 |
76 | MEME 1960 77 | sats≈$152.56 78 | ( 79 | +100.67% 80 | ) 81 |
82 |
83 |
84 | 85 | ); 86 | } 87 | -------------------------------------------------------------------------------- /src/pages/Account/comps/ProModal/index.jsx: -------------------------------------------------------------------------------- 1 | import { Switch, Modal, Button, Row } from "antd"; 2 | import React, { useState, useCallback, useEffect, useMemo } from "react"; 3 | import { useMode } from "hooks/useNostrMarket"; 4 | 5 | import { useSelector, useDispatch } from "react-redux"; 6 | import "./index.scss"; 7 | export default function ProModal() { 8 | const proMode = useSelector(({ user }) => user.proMode); 9 | const npubNostrAccount = useSelector(({ user }) => user.npubNostrAccount); 10 | const [btnLoading, setBtnLoading] = useState(false); 11 | const [willChangeModalValue, setWillChangeModalValue] = useState(false); 12 | const [confirmModalVisible, setConfirmModal] = useState(false); 13 | const { handleQueryMode, handleChangeMode } = useMode(); 14 | 15 | const handleChange = useCallback((value) => { 16 | setConfirmModal(true); 17 | setWillChangeModalValue(value); 18 | }, []); 19 | const onConfirmChangeMode = useCallback(async () => { 20 | setBtnLoading(true); 21 | try { 22 | const ret = await handleChangeMode( 23 | willChangeModalValue ? "open" : "close" 24 | ); 25 | if (ret.code === 0) { 26 | await handleQueryMode(npubNostrAccount); 27 | } 28 | setConfirmModal(false); 29 | window._message.success("Change mode success."); 30 | } catch (e) { 31 | window._message.error(e.message); 32 | } finally { 33 | setBtnLoading(false); 34 | } 35 | }, [ 36 | handleChangeMode, 37 | handleQueryMode, 38 | npubNostrAccount, 39 | willChangeModalValue, 40 | ]); 41 | 42 | return ( 43 | <> 44 | { 52 | setConfirmModal(false); 53 | }} 54 | > 55 |
56 |

57 | Currently in {proMode.value ? "Pro" : "Basic"} mode, please confirm 58 | whether to switch to {willChangeModalValue ? "Pro" : "Basic"} mode 59 |

60 |

61 | Basic Mode:{" "} 62 | Supports all general operations (transactions bundling) and 63 | Chat-to-Trade to execute trades at a speed of 1 Transaction per 64 | Second (TPS). 65 |

66 |

67 | 68 | Professional Mode: 69 | {" "} 70 | Support all functions of Basic Mode and in addition, systematic 71 | trading through REST APIs and Websocket streaming, at a speed of 100 72 | TPS. 73 |

74 |
75 | 76 | 84 | 85 |
86 | 92 | 93 | ); 94 | } 95 | -------------------------------------------------------------------------------- /src/hooks/useGetNostrAccount.js: -------------------------------------------------------------------------------- 1 | import { useCallback, useMemo, useEffect } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { initNostrAccount } from "store/reducer/userReducer"; 4 | import { Modal } from "antd"; 5 | import * as Lockr from "lockr"; 6 | import { useSize } from "ahooks"; 7 | import { nip19 } from "nostr-tools"; 8 | import { useQueryBalance } from "hooks/useNostrMarket"; 9 | import { setConnectNostrModalVisible, setTurnOnNostrDrawerVisible } from "store/reducer/modalReducer"; 10 | import { isInTokenPocket } from "lib/utils/userAgent"; 11 | import { t } from "@lingui/macro"; 12 | export default function useGetNostrAccount() { 13 | const { width } = useSize(document.querySelector("body")); 14 | 15 | const dispatch = useDispatch(); 16 | const { nostrAccount } = useSelector(({ user }) => user); 17 | 18 | const handleGetNostrAccount = useCallback(async () => { 19 | if (!window.nostr) { 20 | if (width > 768) { 21 | const isFirefox = navigator.userAgent.indexOf("Firefox") > -1; 22 | window._notification.warning({ 23 | message: isFirefox 24 | ? "Install the Alby extension on your Firefox" 25 | : "Install the Alby extension on your Chrome", 26 | description: ( 27 | 28 | {`Alby manages your Nostr keys, and you can use your key to sign it.`} 29 | {/* 30 | {`Install now`} 31 | */} 32 | {isFirefox ? ( 33 | 38 | {t`Install now`} 39 | 40 | ) : ( 41 | 46 | {t`Install now`} 47 | 48 | )} 49 | 50 | ) 51 | }); 52 | } else { 53 | if (!isInTokenPocket()) { 54 | dispatch(setConnectNostrModalVisible(true)); 55 | } else { 56 | // check window.nostr & setting、turn on Nostr 57 | if (!window.ethereum) { 58 | // chain 59 | Modal.info({ 60 | width: 326, 61 | footer: null, 62 | closable: true, 63 | title: "Check your network", 64 | 65 | content: ( 66 | <> 67 |
Currently only supported in ERC20, Switch network in wallet
68 | 69 | ) 70 | }); 71 | } else { 72 | dispatch(setTurnOnNostrDrawerVisible(true)); 73 | } 74 | } 75 | } 76 | } else { 77 | let albyNostrAccount = ""; 78 | if (!nostrAccount) { 79 | albyNostrAccount = await window.nostr.getPublicKey(); 80 | dispatch(initNostrAccount(albyNostrAccount)); 81 | Lockr.set("nostrAccount", albyNostrAccount); 82 | } 83 | 84 | return albyNostrAccount; 85 | } 86 | }, [dispatch, nostrAccount, width]); 87 | 88 | return { 89 | handleGetNostrAccount 90 | }; 91 | } 92 | -------------------------------------------------------------------------------- /src/components/NetworkDropdown/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useMemo } from "react"; 2 | import { Dropdown } from "antd"; 3 | import { useNetwork, useSwitchNetwork } from "wagmi"; 4 | import { getIcon } from "config/icons"; 5 | import { t, Trans } from "@lingui/macro"; 6 | import language24Icon from "img/ic_language24.svg"; 7 | import "./NetworkDropdown.scss"; 8 | import "./index.scss"; 9 | import { useDispatch } from "react-redux"; 10 | import { setLanguageModalVisible } from "store/reducer/modalReducer"; 11 | import { WarningOutlined } from "@ant-design/icons"; 12 | import classNames from "classnames"; 13 | export default function NetworkDropdown() { 14 | const { chain, chains } = useNetwork(); 15 | const { 16 | error, 17 | isLoading, 18 | pendingChainId, 19 | switchNetwork, 20 | } = useSwitchNetwork(); 21 | const dispatch = useDispatch(); 22 | const items = useMemo(() => { 23 | let retChains = []; 24 | retChains = chains.map((chainItem) => { 25 | const icon = getIcon(chainItem.id, "network"); 26 | return { 27 | key: chainItem.id, 28 | label: ( 29 | 34 | {chainItem.name} 35 | 36 | ), 37 | icon: ( 38 | {chainItem.name} 43 | ), 44 | }; 45 | }); 46 | if (chains && chains.length > 0) { 47 | retChains = retChains.concat([ 48 | { 49 | type: "divider", 50 | }, 51 | ]); 52 | } 53 | retChains = retChains.concat([ 54 | { 55 | key: "switchLanguage", 56 | label: t`Language`, 57 | icon: ( 58 | {t`Language`} 63 | ), 64 | }, 65 | ]); 66 | return retChains; 67 | }, [chain?.id, chains]); 68 | 69 | const handleMenuClick = useCallback( 70 | (item) => { 71 | if (item.key === "switchLanguage") { 72 | dispatch(setLanguageModalVisible(true)); 73 | } else { 74 | const checkedChainId = Number(item.key); 75 | switchNetwork(checkedChainId); 76 | } 77 | }, 78 | [dispatch, switchNetwork] 79 | ); 80 | const menuProps = { 81 | items, 82 | onClick: handleMenuClick, 83 | }; 84 | const selectedChainIcon = useMemo(() => { 85 | let icon = null; 86 | if (chain) { 87 | icon = getIcon(chain.id, "network"); 88 | } else { 89 | icon = getIcon(10, "network"); 90 | } 91 | return ( 92 | <> 93 | {chain?.unsupported ? ( 94 | 95 | ) : ( 96 | {chain?.name} 97 | )} 98 | 99 | ); 100 | }, [chain]); 101 | return ( 102 | <> 103 | { }} 109 | > 110 | {selectedChainIcon} 111 | 112 | 113 | ); 114 | } 115 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nostr-assets", 3 | "version": "1.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "@davatar/react": "1.8.1", 7 | "@ethersproject/providers": "5.5.1", 8 | "@ethersproject/units": "5.5.0", 9 | "@headlessui/react": "1.6.1", 10 | "@lingui/core": "3.13.3", 11 | "@lingui/react": "3.13.3", 12 | "@reduxjs/toolkit": "^1.9.2", 13 | "@testing-library/jest-dom": "5.16.1", 14 | "@testing-library/react": "11.2.7", 15 | "@testing-library/user-event": "12.8.3", 16 | "@types/node": "18.7.13", 17 | "@types/react": "18.0.17", 18 | "@types/react-dom": "18.0.6", 19 | "@types/react-router-dom": "5.3.3", 20 | "EventEmitter": "^1.0.0", 21 | "ahooks": "^3.7.5", 22 | "antd": "5.5.1", 23 | "await-to-js": "^3.0.0", 24 | "axios": "^1.3.4", 25 | "bignumber.js": "^9.1.1", 26 | "buffer": "^6.0.3", 27 | "classnames": "2.3.1", 28 | "date-fns": "2.27.0", 29 | "dayjs": "^1.11.7", 30 | "echarts": "^5.4.1", 31 | "echarts-for-react": "^3.0.2", 32 | "ethers": "5.6.8", 33 | "framer-motion": "4.1.17", 34 | "graphql": "^16.6.0", 35 | "immer": "^9.0.21", 36 | "lockr": "^0.9.0-beta.2", 37 | "lodash": "4.17.21", 38 | "md5": "^2.3.0", 39 | "qrcode.react": "^3.1.0", 40 | "rc-slider": "9.7.5", 41 | "react": "^18.2.0", 42 | "react-dom": "18.2.0", 43 | "react-error-overlay": "6.0.11", 44 | "react-helmet": "6.1.0", 45 | "react-icons": "4.3.1", 46 | "react-redux": "^8.0.5", 47 | "react-router-dom": "5.3.0", 48 | "react-scripts": "5.0.0", 49 | "react-slick": "^0.29.0", 50 | "sass": "^1.55.0", 51 | "slick-carousel": "^1.8.1", 52 | "urql": "^4.0.0", 53 | "viem": "^0.3.19", 54 | "wagmi": "^1.0.5" 55 | }, 56 | "resolutions": { 57 | "react-error-overlay": "6.0.11" 58 | }, 59 | "scripts": { 60 | "start": "dotenv -e .env.development react-app-rewired start", 61 | "start:test": "dotenv -e .env.test react-app-rewired start", 62 | "build:dev": "CI=false && dotenv -e .env.development react-app-rewired build", 63 | "build:test": "CI=false && dotenv -e .env.test react-app-rewired build", 64 | "extract": "lingui extract", 65 | "compile": "lingui compile" 66 | }, 67 | "browserslist": { 68 | "production": [ 69 | "chrome >= 67", 70 | "edge >= 79", 71 | "firefox >= 68", 72 | "opera >= 54", 73 | "safari >= 14" 74 | ], 75 | "development": [ 76 | "last 1 chrome version", 77 | "last 1 firefox version", 78 | "last 1 safari version" 79 | ] 80 | }, 81 | "devDependencies": { 82 | "@babel/core": "^7.21.3", 83 | "@babel/plugin-proposal-class-properties": "^7.18.6", 84 | "@babel/plugin-proposal-optional-chaining": "^7.21.0", 85 | "@babel/plugin-proposal-private-property-in-object": "^7.21.11", 86 | "@babel/plugin-syntax-dynamic-import": "^7.8.3", 87 | "@lingui/cli": "^3.17.2", 88 | "@lingui/loader": "^3.17.2", 89 | "@lingui/macro": "^3.17.2", 90 | "babel-eslint": "^10.1.0", 91 | "babel-plugin-dynamic-import-node": "^2.3.3", 92 | "customize-cra": "^1.0.0", 93 | "dotenv": "^16.0.3", 94 | "dotenv-cli": "^7.0.0", 95 | "dotenv-expand": "^10.0.0", 96 | "eslint": "^7.11.0", 97 | "eslint-config-prettier": "^8.7.0", 98 | "eslint-plugin-react": "^7.32.2", 99 | "eslint-plugin-react-hooks": "^4.6.0", 100 | "prettier": "2.5.1", 101 | "progress-bar-webpack-plugin": "^2.1.0", 102 | "react-app-rewired": "^2.2.1" 103 | } 104 | } -------------------------------------------------------------------------------- /src/pages/Testnet/comps/QuestList.jsx: -------------------------------------------------------------------------------- 1 | import { List, Typography } from "antd"; 2 | import { CheckOutlined } from "@ant-design/icons"; 3 | import { 4 | usePointsTask, 5 | usePointsTaskQuests, 6 | } from "hooks/graphQuery/useTestnet"; 7 | import { useMemo, useEffect } from "react"; 8 | import { ReactComponent as Success } from "img/success.svg"; 9 | export default function QuestList({ npubNostrAccount }) { 10 | const staticData = useMemo(() => { 11 | return [ 12 | { 13 | label: "Register Nostr Account", 14 | }, 15 | { 16 | label: "Follow TokenManager", 17 | }, 18 | { 19 | label: "Follow MarketManager", 20 | }, 21 | { 22 | label: "Follow NostrAssets Nostr account", 23 | }, 24 | { 25 | label: 26 | "Approve Market Manager as Operator with at least 1000 Testnet USDT or other tokens", 27 | }, 28 | { 29 | label: "Query address book", 30 | }, 31 | ]; 32 | }, []); 33 | const nostrAddress = useMemo(() => { 34 | if (npubNostrAccount) { 35 | return nip19.decode(npubNostrAccount).data; 36 | } else { 37 | return false; 38 | } 39 | }, [npubNostrAccount]); 40 | const { data, fetching } = usePointsTask(); 41 | const { data: userData, reexcuteQuery } = usePointsTaskQuests({ 42 | address: nostrAddress, 43 | }); 44 | useEffect(() => { 45 | setInterval(() => { 46 | reexcuteQuery(); 47 | }, 30000); 48 | return () => null; 49 | }, [reexcuteQuery]); 50 | const questsData = useMemo(() => { 51 | return staticData.map((item) => { 52 | const row = userData.find((k) => k.task_type === item.label); 53 | const graphRow = data.find((j) => item.label.indexOf(j.task_type) > -1); 54 | if (row) { 55 | return { 56 | ...graphRow, 57 | task_type: item.label, 58 | user_reward_points: row.reward_points, 59 | }; 60 | } else { 61 | return graphRow 62 | ? { ...graphRow, task_type: item.label } 63 | : { ...item, task_type: item.label }; 64 | } 65 | }); 66 | }, [staticData, data, userData]); 67 | // 68 | 69 | return ( 70 | <> 71 |
72 |
One-time Tester Quests
73 | { 79 | let rewardPoint = item.reward_points; 80 | if (item.halve_time && item.reward_halve_points) { 81 | if (Date.now() > new Date(item.halve_time).getTime()) { 82 | rewardPoint = item.reward_halve_points; 83 | } 84 | } 85 | return ( 86 | 87 | {item.task_type} 88 | +{rewardPoint} 89 | 90 | {item.user_reward_points ? ( 91 | 96 | ) : ( 97 | "--" 98 | )} 99 | 100 | 101 | ); 102 | }} 103 | /> 104 |
105 | 106 | ); 107 | } 108 | -------------------------------------------------------------------------------- /src/components/RelayList/index.scss: -------------------------------------------------------------------------------- 1 | .relay-popover{ 2 | width: 400px; 3 | max-width: 90%; 4 | } 5 | .relay-url-list-box{ 6 | .relays-text{ 7 | padding: 5px 10px; 8 | cursor: pointer; 9 | } 10 | .relays-btn{ 11 | height: 36px; 12 | // margin-left: 20px; 13 | position: relative; 14 | margin-left: 2.4rem; 15 | padding: 4px 15px 4px 20px; 16 | &::before{ 17 | content: ""; 18 | width: 6px; 19 | height: 6px; 20 | background-color: var(--color-green-light); 21 | border-radius: 6px; 22 | position: absolute; 23 | top: 50%; 24 | left: 8px; 25 | transform: translateY(-50%); 26 | } 27 | &__disconnected{ 28 | &::before { 29 | background-color: var(--color-red); 30 | } 31 | } 32 | } 33 | } 34 | 35 | .relay-url-list{ 36 | // width: 500px; 37 | // max-width: 100%; 38 | // min-height: 200px; 39 | .relay-url-title-box{ 40 | display: flex; 41 | justify-content: space-between; 42 | align-items: center; 43 | padding-bottom: 8px; 44 | } 45 | .relay-url-title{ 46 | font-size: 16px; 47 | line-height: 26px; 48 | } 49 | .relay-url-title-desc{ 50 | font-size: 12px; 51 | line-height: 16px; 52 | color: var(--color-text-dark); 53 | margin-bottom: 10px; 54 | } 55 | .relay-add{ 56 | margin-left: 10px; 57 | padding: 1px 7px; 58 | height: auto; 59 | font-size: 12px; 60 | } 61 | .Checkbox-box{ 62 | display: flex; 63 | justify-content: space-between; 64 | width: 100%; 65 | position: relative; 66 | } 67 | .ant-checkbox-disabled .ant-checkbox-inner{ 68 | background-color: #38c89d; 69 | border-color: #38c89d; 70 | border-radius: 10px; 71 | } 72 | .nostr-checkbox-connected .ant-checkbox .ant-checkbox-inner { 73 | background-color: #38c89d; 74 | border-color: #38c89d; 75 | } 76 | .nostr-checkbox-disconnected .ant-checkbox .ant-checkbox-inner{ 77 | background-color: var(--color-red); 78 | border-color: var(--color-red); 79 | 80 | } 81 | .CheckOutlined-btn{ 82 | vertical-align: sub; 83 | padding: 0; 84 | margin-left: 5px; 85 | color: var(--color-green-light); 86 | &:hover{ 87 | color: var(--color-green-light); 88 | } 89 | } 90 | 91 | .CloseOutlined-btn{ 92 | vertical-align: sub; 93 | padding: 0; 94 | margin-left: 5px; 95 | color: var(--color-text-dark); 96 | &:hover{ 97 | color: var(--color-green-light); 98 | } 99 | } 100 | // .ant-checkbox-group .ant-checkbox-wrapper.offical{ 101 | // .ant-checkbox-disabled+span{ 102 | // color: var(--color-teal); 103 | // } 104 | 105 | // } 106 | .ant-checkbox-group .ant-checkbox-wrapper{ 107 | font-size: 12px; 108 | .ant-typography{ 109 | display: inline-block; 110 | margin-bottom: 0; 111 | color: var(--color-green-light); 112 | 113 | .ant-typography-copy{ 114 | position: absolute; 115 | top: 0; 116 | right: 0; 117 | } 118 | } 119 | 120 | } 121 | .DeleteOutlined-btn{ 122 | vertical-align: sub; 123 | padding: 0; 124 | margin-left: 5px; 125 | color: var(--color-text-dark); 126 | &:hover{ 127 | color: var(--color-green-light); 128 | } 129 | } 130 | .ant-checkbox-group{ 131 | display: block; 132 | .ant-checkbox-wrapper{ 133 | display: flex; 134 | } 135 | .ant-checkbox-wrapper+.ant-checkbox-wrapper{ 136 | margin-inline-start: 0; 137 | } 138 | } 139 | 140 | } 141 | .relay-response-time { 142 | margin-top:20px; 143 | font-size:14px; 144 | &__value { 145 | color:var(--color-green-light); 146 | padding-left:10px; 147 | } 148 | } -------------------------------------------------------------------------------- /src/pages/Testnet/comps/UserPointerInfo.jsx: -------------------------------------------------------------------------------- 1 | import EllipsisMiddle from "components/EllipsisMiddle"; 2 | import { usePointsAccount } from "hooks/graphQuery/useTestnet"; 3 | import TokenTip from "./TokenTip"; 4 | import { useMemo, useEffect } from "react"; 5 | import { nip19 } from "nostr-tools"; 6 | export default function UserPointerInfo({ npubNostrAccount }) { 7 | const nostrAddress = useMemo(() => { 8 | if (npubNostrAccount) { 9 | return nip19.decode(npubNostrAccount).data; 10 | } else { 11 | return false; 12 | } 13 | }, [npubNostrAccount]); 14 | const { data, total, fetching, reexcuteQuery } = usePointsAccount({ 15 | id: nostrAddress, 16 | }); 17 | useEffect(() => { 18 | setInterval(() => { 19 | reexcuteQuery(); 20 | }, 30000); 21 | return () => null; 22 | }, [reexcuteQuery]); 23 | const rank = useMemo(() => { 24 | if (total && data?.ranking) { 25 | const num = (data?.ranking / total) * 100; 26 | if (num <= 10) { 27 | return "10%"; 28 | } else if (num > 10 && num <= 20) { 29 | return "20%"; 30 | } else if (num > 20 && num <= 30) { 31 | return "30%"; 32 | } else if (num > 30 && num <= 50) { 33 | return "50%"; 34 | } else { 35 | return "51%~100%"; 36 | } 37 | } else { 38 | return false; 39 | } 40 | }, [data?.ranking, total]); 41 | // 42 | return ( 43 |
44 |
45 |
46 | 47 | My Nostr Address 48 | 49 | 50 | 51 | {npubNostrAccount ? ( 52 | 53 | {npubNostrAccount} 54 | 55 | ) : ( 56 | "--" 57 | )} 58 | 59 |
60 |
61 | 62 | My Current Pioneer Points 63 | 64 | 65 | {npubNostrAccount ? ( 66 | <> 67 | 68 | {" "} 69 | {(data?.task_points || 0) + (data?.reward_points || 0)} 70 | 71 | {` (${data?.task_points || 0} quest points + ${ 72 | data?.reward_points || 0 73 | } bonus points)`} 74 | 75 | 76 | 77 | 78 | 79 | 80 | ) : ( 81 | "--" 82 | )} 83 | 84 |
85 |
86 | 87 | My Current Rank Tier 88 | 89 | 90 | {npubNostrAccount ? ( 91 | 92 | {rank ? `Top ${rank} of all participants` : "--"} 93 | {/* {rank ? `(${data?.ranking}/${total})` : ""} */} 94 | 95 | ) : ( 96 | "--" 97 | )} 98 | 99 |
100 |
101 |
102 | ); 103 | } 104 | -------------------------------------------------------------------------------- /src/pages/Withdraw/comps/WithdrawForm.scss: -------------------------------------------------------------------------------- 1 | .withdraw-form { 2 | margin: 20px auto 0; 3 | display: flex; 4 | justify-content: center; 5 | .ant-form-item-label { 6 | text-align: left; 7 | } 8 | .network-selector-btn { 9 | width: 111px; 10 | max-width: 50%; 11 | text-align: center; 12 | } 13 | .deposit-plat { 14 | padding-left: 10px; 15 | color: var(--color-green); 16 | } 17 | .deposit-inscriptions { 18 | padding: 0 0 0 174px; 19 | margin-bottom: 20px; 20 | &-label { 21 | padding-bottom: 10px; 22 | } 23 | &-list { 24 | border: 1px solid #333; 25 | border-radius: 10px; 26 | width: 490px; 27 | min-height: 100px; 28 | max-height: 200px; 29 | overflow-y: auto; 30 | display: flex; 31 | justify-content: center; 32 | flex-wrap: wrap; 33 | .ant-card { 34 | margin: 10px; 35 | } 36 | } 37 | &-empty { 38 | margin: 10px auto; 39 | } 40 | } 41 | .deposit-balance { 42 | padding-left: 175px; 43 | padding-bottom: 20px; 44 | &-value { 45 | color: var(--color-green); 46 | &__red { 47 | color: var(--color-red); 48 | } 49 | } 50 | } 51 | .withdraw-amount { 52 | &-row { 53 | position: relative; 54 | } 55 | &-balance { 56 | font-size: 12px; 57 | } 58 | &-remain { 59 | position: absolute; 60 | right: 55px; 61 | top: -20px; 62 | color: rgba(255, 255, 255, 0.45); 63 | font-size: 12px; 64 | } 65 | &-more-address { 66 | cursor: pointer; 67 | } 68 | &-suffix { 69 | font-size: 14px; 70 | .ant-btn-link { 71 | padding: 4px 10px; 72 | color: var(--color-green); 73 | } 74 | &__symbol { 75 | padding-left: 10px; 76 | } 77 | } 78 | } 79 | .lightning-withdraw { 80 | display: flex; 81 | flex-direction: column; 82 | &-amount { 83 | color: var(--color-text-light); 84 | font-size: 16px; 85 | font-weight: bold; 86 | } 87 | &-balance { 88 | font-size: 14px; 89 | } 90 | &-tip { 91 | background-color: var(--color-text-yellow); 92 | opacity: 0.6; 93 | width: 100%; 94 | padding: 10px; 95 | border-radius: 20px; 96 | margin-bottom: 20px; 97 | } 98 | } 99 | &-switch__tip { 100 | display: block; 101 | font-size: 12px; 102 | color: var(--color-red); 103 | padding-left: 30px; 104 | } 105 | .mb20 { 106 | margin-bottom: 20px; 107 | } 108 | @media screen and (max-width: 768px) { 109 | .deposit-balance { 110 | padding-left: 0; 111 | } 112 | .deposit-inscriptions { 113 | padding: 0; 114 | margin-bottom: 20px; 115 | width: 100%; 116 | &-list { 117 | width: 100%; 118 | .ant-card { 119 | margin: 5px; 120 | } 121 | } 122 | } 123 | .fixed-btn { 124 | position: fixed; 125 | bottom: 0; 126 | left: 0; 127 | width: 100vw; 128 | padding: 15px; 129 | background: var(--color-layer-base); 130 | margin-bottom: 0; 131 | border-top: 1px solid var(--color-border-grey); 132 | z-index: 100; 133 | } 134 | } 135 | } 136 | .deposit-brc20-fees { 137 | display: flex; 138 | width: 340px; 139 | height: 100px; 140 | .ant-radio-button-wrapper { 141 | height: auto; 142 | padding: 5px; 143 | flex: 1; 144 | 145 | .fee-title { 146 | font-size: 16px; 147 | font-weight: bold; 148 | display: flex; 149 | justify-content: center; 150 | align-items: center; 151 | } 152 | .fee-tip { 153 | font-size: 12px; 154 | color: var(--color-text-dark); 155 | line-height: 100%; 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/pages/Account/comps/Transfer/index.scss: -------------------------------------------------------------------------------- 1 | .sell-button-checked { 2 | .ant-radio-button-checked { 3 | background: var(--color-orange); 4 | border-color: var(--color-orange); 5 | } 6 | .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled) { 7 | background: var(--color-orange); 8 | border-color: var(--color-orange); 9 | 10 | } 11 | .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover { 12 | background: var(--color-orange); 13 | border-color: var(--color-orange); 14 | } 15 | .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled)::before { 16 | background: var(--color-orange); 17 | } 18 | 19 | } 20 | .select-token-name { 21 | font-size: 14px; 22 | } 23 | .select-token-balance { 24 | font-size:12px; 25 | color:var(--color-text-dark); 26 | padding-left:5px; 27 | } 28 | .transfer-form { 29 | width:780px; 30 | padding:50px 20px 0; 31 | max-width: 100%; 32 | .transfer-value { 33 | padding-left:60px; 34 | } 35 | .ant-form-item{ 36 | margin-bottom: 40px; 37 | .ant-select-selection-search-input{ 38 | font-size: 14px; 39 | } 40 | } 41 | .ant-form-item.amount-form-item{ 42 | .ant-form-item-control-input-content{ 43 | display: flex; 44 | align-items: center; 45 | } 46 | } 47 | .ant-form-item .ant-form-item-label{ 48 | text-align: start; 49 | } 50 | .limit-buy-available{ 51 | font-size: 12px; 52 | color: var(--color-text-dark); 53 | text-align: center; 54 | // margin-top: 15px; 55 | } 56 | .transfer-submit-btn { 57 | min-width:180px; 58 | &__sell { 59 | background-color:var(--color-orange); 60 | &:hover { 61 | background-color:var(--color-orange); 62 | } 63 | } 64 | } 65 | &-usdt { 66 | padding-left:10px; 67 | } 68 | .transfer-input { 69 | width: 490px; 70 | // height:32px; 71 | } 72 | .transfer-form-usdt{ 73 | font-size:14px; 74 | // vertical-align: sub; 75 | } 76 | .suffix-btn { 77 | width:45px; 78 | font-size:14px; 79 | } 80 | 81 | .transfer-form-balance { 82 | position:absolute; 83 | top:-30px; 84 | left:-45px; 85 | width:100%; 86 | display: flex; 87 | color:var(--color-text-dark); 88 | text-align: center; 89 | font-size:12px; 90 | &-container { 91 | width:100%; 92 | padding-right:20px; 93 | 94 | } 95 | } 96 | &-total-value { 97 | font-size: 16px; 98 | span{ 99 | font-size: 12px; 100 | } 101 | } 102 | // .transfer-form-total-stats { 103 | // margin-bottom:0px; 104 | // } 105 | 106 | } 107 | @media screen and (max-width: 768px) { 108 | .transfer-form { 109 | width:100%; 110 | padding:20px 0 0; 111 | .ant-form-item{ 112 | margin-bottom: 20px; 113 | // display: block; 114 | .ant-form-item-row{ 115 | display: block; 116 | } 117 | } 118 | .ant-form-item .ant-form-item-label{ 119 | padding: 0; 120 | flex: initial; 121 | } 122 | .ant-form-item .ant-form-item-control{ 123 | flex: 1; 124 | } 125 | .ant-form-item .ant-form-item-label >label{ 126 | display: flex; 127 | // width:90px; 128 | font-size: 12px; 129 | } 130 | 131 | .transfer-input { 132 | width: calc(100% - 45px); 133 | height: 32px; 134 | } 135 | .transfer-form-usdt{ 136 | font-size:12px; 137 | } 138 | .suffix-btn { 139 | font-size:12px; 140 | } 141 | .transfer-select{ 142 | width: 100%; 143 | } 144 | } 145 | .select-token-name { 146 | font-size: 12px; 147 | } 148 | .select-token-balance { 149 | font-size:10px; 150 | } 151 | } 152 | .transfer-modal{ 153 | .ant-modal-title{ 154 | padding-bottom: 10px; 155 | text-align: center; 156 | } 157 | } -------------------------------------------------------------------------------- /src/components/Footer/Footer.scss: -------------------------------------------------------------------------------- 1 | .Footer{ 2 | width: 100%; 3 | background: var(--color-layer-base) !important; 4 | height: 36px; 5 | display: flex; 6 | justify-content: space-between; 7 | align-items: center; 8 | font-size: 13px; 9 | color: var(--color-text-dark); 10 | border-top: 1px solid var(--color-border-grey); 11 | box-sizing: border-box; 12 | position: relative; 13 | 14 | // &::after{ 15 | // content: ""; 16 | // width: 1px; 17 | // height: 24px; 18 | // position: absolute; 19 | // top: 6px; 20 | // right: 130px; 21 | // background: ; 22 | // } 23 | .Footer-left{ 24 | width: 100%; 25 | height: 24px; 26 | line-height: 24px; 27 | padding-right: 25px; 28 | // border-right: 1px solid var(--color-border-grey); 29 | box-sizing: border-box; 30 | position: relative; 31 | &::before{ 32 | content: ""; 33 | width: 6px; 34 | height: 6px; 35 | border-radius: 3px; 36 | background: var(--color-green); 37 | position: absolute; 38 | top: 50%; 39 | left: 12px; 40 | transform: translateY(-50%); 41 | } 42 | .Operational{ 43 | padding-left: 25px; 44 | cursor: pointer; 45 | } 46 | } 47 | .Footer-right{ 48 | height: 24px; 49 | line-height: 24px; 50 | padding: 0 20px; 51 | border-left: 1px solid var(--color-border-grey); 52 | } 53 | } 54 | .Footer-wrapper { 55 | text-align: center; 56 | padding-top: 4rem; 57 | background: #16182e; 58 | width: 100vw; 59 | position: absolute; 60 | transform: translateX(-50%); 61 | left: 50%; 62 | height: 20rem; 63 | bottom: 0; 64 | border-top: 1px solid #282b4c; 65 | } 66 | 67 | .Footer-logo { 68 | margin-bottom: 2.4rem; 69 | display: flex; 70 | justify-content: center; 71 | align-items: center; 72 | } 73 | 74 | .Footer-logo img { 75 | height: 2.65rem; 76 | } 77 | 78 | .Footer-social-link-block { 79 | margin: 0 auto; 80 | display: flex; 81 | justify-content: center; 82 | margin-bottom: 2.4rem; 83 | } 84 | .Footer-link { 85 | color: #a0a3c4; 86 | font-size: var(--font-base); 87 | line-height: 1.85rem; 88 | font-weight: normal; 89 | text-decoration: none; 90 | cursor: pointer; 91 | } 92 | .Footer-link:hover { 93 | color: white; 94 | } 95 | 96 | .Footer-social-link-block .App-social-link { 97 | margin: 0 3.2rem; 98 | display: flex; 99 | width: 3.2rem; 100 | height: 3.2rem; 101 | align-items: center; 102 | justify-content: center; 103 | } 104 | 105 | .Footer-social-link-block .App-social-link:hover img { 106 | filter: brightness(0) invert(1); 107 | } 108 | 109 | .Footer-copyright { 110 | padding: 1.6rem; 111 | } 112 | 113 | .Footer-copyright__text { 114 | font-family: Circular Std; 115 | font-size: 1.3rem; 116 | line-height: 1.3rem; 117 | letter-spacing: -0.41px; 118 | color: #a0a3c4; 119 | display: block; 120 | } 121 | .Footer-links { 122 | padding-bottom: 4rem; 123 | display: flex; 124 | justify-content: center; 125 | } 126 | .Footer-links > a:not(:last-child), 127 | .Footer-links > .a:not(:last-child) { 128 | padding-right: 2rem; 129 | } 130 | 131 | @media (max-width: 900px) { 132 | .Footer-social-link-block .App-social-link { 133 | margin: 0 1.6rem; 134 | } 135 | } 136 | 137 | @media (max-width: 580px) { 138 | .Footer-links { 139 | flex-direction: column; 140 | } 141 | .Footer-links > a { 142 | padding-right: 0; 143 | margin-bottom: 0.5rem; 144 | display: block; 145 | } 146 | .Footer-social-link-block { 147 | margin-bottom: 1.4rem; 148 | } 149 | .Footer-wrapper { 150 | padding-top: 2.5rem; 151 | } 152 | .home { 153 | height: 23rem; 154 | } 155 | .Footer-links > a:not(:last-child), 156 | .Footer-links > .a:not(:last-child) { 157 | padding-right: 0; 158 | } 159 | .Footer-social-link-block .App-social-link { 160 | margin: 0 0.8rem; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/hooks/unisatWallet/useUnisatWalletSdk.js: -------------------------------------------------------------------------------- 1 | import { useState, useCallback, useEffect, useRef } from "react"; 2 | import { setAccount, setChainId } from 'store/reducer/userReducer' 3 | import { useDispatch } from "react-redux"; 4 | export default function useUnisatSdk() { 5 | const [network, setNetwork] = useState(null); 6 | const dispatch = useDispatch(); 7 | const [unisatAccount, setUnisatAccount] = useState(null); 8 | const getAccount = useCallback(async () => { 9 | const ret = await window.unisat.getAccounts().catch(e => { }) 10 | if (ret && ret.length > 0) { 11 | setUnisatAccount(ret[0]) 12 | dispatch(setAccount(ret[0])) 13 | } 14 | return ret ? ret[0] : null; 15 | }, [dispatch]); 16 | const connectUnisat = useCallback(async () => { 17 | const requestRet = await window.unisat.requestAccounts(); 18 | if (requestRet.length > 0) { 19 | setUnisatAccount(requestRet[0]) 20 | dispatch(setAccount(requestRet[0])) 21 | } 22 | return requestRet 23 | }, [dispatch]); 24 | const disconnectUnisat = useCallback(() => { 25 | setUnisatAccount(null); 26 | setNetwork(null); 27 | dispatch(setChainId(null)) 28 | }, [dispatch]); 29 | const handleAccountsChange = useCallback(async (accounts) => { 30 | if (accounts && accounts.length > 0) { 31 | setUnisatAccount(accounts[0]); 32 | dispatch(setAccount(accounts[0])) 33 | } 34 | }, [dispatch]) 35 | 36 | const getNetwork = useCallback(async () => { 37 | const retNetwork = window.unisat.isTokenPocket ? window.unisat.getNetwork() : await window.unisat.getNetwork().catch((e => { })); 38 | if (retNetwork) { 39 | setNetwork(retNetwork); 40 | dispatch(setChainId(retNetwork)) 41 | } 42 | return retNetwork; 43 | }, [dispatch]); 44 | 45 | const switchNetwork = useCallback(async (switchTo) => { 46 | const ret = await unisat.switchNetwork(switchTo); 47 | if (ret) { 48 | setNetwork(switchTo); 49 | } 50 | }, []); 51 | 52 | const signMessage = useCallback((message) => { 53 | return window.unisat.signMessage(message); 54 | }, []); 55 | 56 | const getInscriptions = useCallback((cursor, size) => { 57 | return window.unisat.getInscriptions(cursor, size); 58 | }, []); 59 | 60 | const sendInscription = useCallback((to, inscriptionId, options = {}) => { 61 | return window.unisat.sendInscription(to, inscriptionId, options); 62 | }, []); 63 | 64 | const getInitStatus = () => { 65 | return window.unisat._state.initialized; 66 | }; 67 | const checkIsDestroyed = () => { 68 | try { 69 | let isDestroyed = true; 70 | window.unisat.getAccounts().then((t) => { 71 | isDestroyed = false; 72 | }); 73 | return new Promise((resolve) => { 74 | setTimeout(() => { 75 | if (!isDestroyed) { 76 | resolve(false); 77 | } else { 78 | resolve(true); 79 | } 80 | }, 1000); 81 | }); 82 | } catch { } 83 | }; 84 | 85 | useEffect(() => { 86 | if (window.unisat) { 87 | window.unisat.on("accountsChanged", handleAccountsChange); 88 | window.unisat.on("networkChanged", switchNetwork); 89 | } 90 | return () => { 91 | if (window.unisat) { 92 | window.unisat.removeListener("accountsChanged", handleAccountsChange); 93 | window.unisat.removeListener("networkChanged", switchNetwork); 94 | } 95 | }; 96 | }, [handleAccountsChange, switchNetwork]); 97 | 98 | useEffect(() => { 99 | let timer = null; 100 | if (window.unisat) { 101 | timer = setTimeout(() => { 102 | getAccount(); 103 | getNetwork(); 104 | }, 1000) 105 | } 106 | return () => { 107 | clearTimeout(timer); 108 | } 109 | }, [getAccount, getNetwork]); 110 | 111 | return { 112 | isReady: !!window.unisat, 113 | network, 114 | unisatAccount: unisatAccount, 115 | connectUnisat, 116 | checkIsDestroyed, 117 | disconnectUnisat, 118 | switchNetwork, 119 | signMessage, 120 | getAccount, 121 | getInitStatus, 122 | getInscriptions, 123 | sendInscription 124 | }; 125 | } 126 | -------------------------------------------------------------------------------- /src/pages/Explore/index.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useRef, useMemo, useCallback, useEffect } from "react"; 2 | import { Layout, Menu, theme } from "antd"; 3 | import { t } from "@lingui/macro"; 4 | import { BarChartOutlined } from "@ant-design/icons"; 5 | import { 6 | Switch, 7 | Route, 8 | Link, 9 | useHistory, 10 | useRouteMatch, 11 | Redirect, 12 | } from "react-router-dom"; 13 | import MarketEvents from "./MarketEvents"; 14 | // import Orders from "./Orders"; 15 | import TokenEvents from "./TokenEvents"; 16 | import Markets from "./Markets"; 17 | import Banner from "components/Banner"; 18 | // import TokenSlider from "./comps/TokenSlider" 19 | import "./index.scss"; 20 | const { Header, Content } = Layout; 21 | function getItem(label, key, icon, children) { 22 | return { 23 | label, 24 | key, 25 | icon, 26 | children, 27 | }; 28 | } 29 | export default function Explore() { 30 | const [selectedKeys, setSelectedKeys] = useState([]); 31 | const match = useRouteMatch(); 32 | const history = useHistory(); 33 | const items = useMemo(() => { 34 | return [ 35 | getItem( 36 | {t`Token Events`}, 37 | "tokenevents" 38 | ), 39 | getItem( 40 | {t`Market Events`}, 41 | "marketevents" 42 | ), 43 | // getItem({t`Orders`}, "orders"), 44 | // getItem({t`Markets`}, "markets"), 45 | ]; 46 | }, [match.url]); 47 | const pathNames = useMemo(() => { 48 | return { 49 | [match.url]: ["tokenevents"], 50 | [match.url + "/token-events"]: ["tokenevents"], 51 | // [match.url + "/orders"]: ["orders"], 52 | [match.url + "/market-events"]: ["marketevents"], 53 | // [match.url + "/market"]: ["markets"], 54 | }; 55 | }, [match.url]); 56 | const switchMemo = useMemo(() => { 57 | return ( 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | {/* 69 | 70 | */} 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | ); 79 | }, [match.url]); 80 | 81 | useEffect(() => { 82 | setSelectedKeys(pathNames[history.location.pathname]); 83 | }, [history.location.pathname, pathNames]); 84 | const selectedCilck = useCallback(({ item, key, keyPath, domEvent }) => { 85 | setSelectedKeys(key); 86 | }, []); 87 | const historyTo = useCallback(() => { 88 | history.push(`${match.url}/markets`); 89 | setSelectedKeys([]); 90 | }, [history, match.url]); 91 | 92 | return ( 93 |
94 | 95 | {/*
96 | 97 |
*/} 98 | {/* */} 99 |
100 | 108 | {/*
historyTo()}>{t`Markets`}
*/} 109 |
110 | {/* */} 111 | 112 | 113 |
{switchMemo}
114 |
115 |
116 |
117 | ); 118 | } 119 | -------------------------------------------------------------------------------- /src/components/Common/ConnectWallet.jsx: -------------------------------------------------------------------------------- 1 | import { Trans, t } from "@lingui/macro"; 2 | import { Modal } from "antd"; 3 | import ConnectWalletButton from "./ConnectWalletButton"; 4 | import connectWalletImg from "img/ic_wallet_24.svg"; 5 | import "./ConnectWallet.scss"; 6 | import { useNetwork } from "wagmi"; 7 | import { useDispatch, useSelector } from "react-redux"; 8 | import { setWalletConnectModalVisible } from "store/reducer/modalReducer"; 9 | import { useCallback } from "react"; 10 | import { isInTokenPocket, isApple } from "lib/utils/userAgent"; 11 | import useNostrDisconnect from "hooks/useNostrDisconnect"; 12 | 13 | export function ConnectWalletWithOnlyDeposit({ 14 | connectType, 15 | btnText = t`Connect wallet`, 16 | }) { 17 | const { chains } = useNetwork(); 18 | const dispatch = useDispatch(); 19 | const { handleDisconnect } = useNostrDisconnect(); 20 | const selectedTokenPlatform = useSelector( 21 | ({ user }) => user.selectedTokenPlatform 22 | ); 23 | /* const { selectedTokenPlatform } = useSelector(({ user }) => user); */ 24 | const handleConnect = useCallback(() => { 25 | // handleDisconnect(); 26 | if (connectType === "switch" && !isInTokenPocket()) { 27 | handleDisconnect(); 28 | } 29 | // if (isInTokenPocket() && window?.ethereum?.networkVersion != chains[0].id && isApple()) { 30 | // Modal.info({ 31 | // width: 326, 32 | // footer: null, 33 | // closable: true, 34 | // title: "Check your network", 35 | // content: ( 36 | // <> 37 | //
{t`​Deposit only supported on Goerli Network at the moment. Switch network in wallet!`}
38 | // 39 | // ) 40 | // }); 41 | // return false; 42 | // } 43 | if (selectedTokenPlatform === "BTC" && isInTokenPocket() && isApple()) { 44 | Modal.info({ 45 | width: 326, 46 | footer: null, 47 | closable: true, 48 | title: "Check your network", 49 | content: ( 50 | <> 51 |
{t`​Deposit only supported on Goerli Network at the moment. Switch network in wallet!`}
52 | 53 | ), 54 | }); 55 | return false; 56 | } 57 | if ( 58 | selectedTokenPlatform === "BTC" && 59 | isInTokenPocket() && 60 | !window.unisat 61 | ) { 62 | Modal.info({ 63 | width: 326, 64 | footer: null, 65 | closable: true, 66 | title: "Check your network", 67 | content: ( 68 | <> 69 |
{t`​You selected an BRC20 token to deposit, please click to switch your wallet connect from ERC20 to BRC20.`}
70 | 71 | ), 72 | }); 73 | return false; 74 | } 75 | if (selectedTokenPlatform === "ETH" && isInTokenPocket() && window.unisat) { 76 | Modal.info({ 77 | width: 326, 78 | footer: null, 79 | closable: true, 80 | title: "Check your network", 81 | content: ( 82 | <> 83 |
{t`You selected an ERC20 token to deposit, please click to switch your wallet connect from BRC20 to Goerli Network.`}
84 | 85 | ), 86 | }); 87 | return false; 88 | } 89 | dispatch(setWalletConnectModalVisible(true)); 90 | }, [connectType, dispatch, handleDisconnect, selectedTokenPlatform]); 91 | return ( 92 |
93 | 99 | {btnText} 100 | 101 |
102 | ); 103 | } 104 | export default function ConnectWallet() { 105 | const dispatch = useDispatch(); 106 | return ( 107 |
108 |
109 | Connect your wallet to deposit funds and start trading. 110 |
111 | dispatch(setWalletConnectModalVisible(true))} 113 | imgSrc={connectWalletImg} 114 | > 115 | Connect wallet 116 | 117 |
118 | ); 119 | } 120 | --------------------------------------------------------------------------------