├── 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 |
19 | {imgSrc && }
20 | {children}
21 |
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 |
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 |
27 | OK
28 |
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 | You need to enable JavaScript to run this app.
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 | {
32 | modalRef.current.handleCancel();
33 | }}
34 | style={{ width: "100px" }}
35 | >
36 | Ok
37 |
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 | {
}
28 |
29 |
{locales[item]}
30 |
31 |
32 | {currentLanguage.current === 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) ? "🫐" :
}
34 |
35 |
{locales[item]}
36 |
37 |
38 | {currentLanguage.current === 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 |
52 | Switch Network
53 |
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 |
24 | Open
25 |
26 | */}
27 |
36 |
37 |
38 |
39 | {/*
40 | OK
41 | */}
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 |
7 |
8 |
9 |
10 |
11 |
12 |
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 |
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 |
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 |
82 | Confirm
83 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------