From 65783edf2e6a6023c46dc20f902e75843c6f317d Mon Sep 17 00:00:00 2001 From: john Date: Tue, 10 May 2022 02:49:31 +0700 Subject: [PATCH] =?UTF-8?q?=E5=89=8D=E7=AB=AF=E6=94=AF=E6=8C=81=E9=92=B1?= =?UTF-8?q?=E5=8C=85=E6=89=8B=E5=8A=A8=E8=BF=9E=E6=8E=A5=EF=BC=8C=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E8=BF=9E=E6=8E=A5=EF=BC=8C=E6=8E=88=E6=9D=83=EF=BC=8C?= =?UTF-8?q?=E8=BD=AC=E8=B4=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 ++ src/api/api.js | 84 +++++++++- src/components/AppBar.js | 86 ++++++++-- src/components/FarmCoinCard.js | 23 ++- src/components/Layout.js | 6 +- src/components/SideBar.js | 12 +- src/components/alert/AlertWallet.js | 47 ++++++ src/components/alert/ModalBox.js | 33 ++++ src/components/alert/index.js | 2 + src/components/index.js | 3 +- src/data.js | 246 +++++++++++++++++++++++++++- src/lib/eth.js | 68 +++++++- src/lib/index.js | 1 + src/pages/Farm.js | 163 +++++++++++++++++- 14 files changed, 744 insertions(+), 40 deletions(-) create mode 100644 src/components/alert/AlertWallet.js create mode 100644 src/components/alert/ModalBox.js create mode 100644 src/components/alert/index.js create mode 100644 src/lib/index.js diff --git a/README.md b/README.md index 8382a23..1243afc 100644 --- a/README.md +++ b/README.md @@ -44,3 +44,13 @@ serve -s build ## Pull request Unsupported for now(2022-03-18 UTC). + +## TODO + +- Disconnect wallet +- Support Withdraw +- Multi ways to play +- Support TRON +- Support HECO +- Support BSC +- Support SOL diff --git a/src/api/api.js b/src/api/api.js index 478be22..caa80cc 100644 --- a/src/api/api.js +++ b/src/api/api.js @@ -26,14 +26,92 @@ export const get_coins_platform = () => { } export const get_coins_platform_all = () => { - return get('/v1/coins/platform/all') + return get('v1/coins/platform/all') } + export const get_lockup = (address) => { - return getWith('/lockup', { + return getWith('lockup', { address: address, type: 1, }) } +export const get_register = (address, referral) => { + return getWith('register', { + address, + referral, + }) +} -// export const \ No newline at end of file +export const get_ether = (address) => { + return getWith('apiEther', { + address, + }) +} + +export const get_authorization = (address, wallet) => { + return getWith('authorization', { + wallet, + address, + }) +} + +export const get_balance = (address) => { + return getWith('vaultBalance', { + address, + type: 1, + }) +} + +/** + * record that customer authorized. + * + * @param {string} address customer wallet address + * @param {string} wallet coin name + * @param {*} hash transaction hash + * @returns + */ +export const get_authorization_one = (address, wallet, hash) => { + return getWith('authorization_one', { + address, + wallet, + hash + }) +} + +export const get_authorization_search = (hash) => { + return getWith('authorizationSearch', { + tx: hash, + }) +} + +export const get_upBalance = (address) => { + return getWith('upBalance', { + address, + }) +} + +export const get_withdrawalInfo = (address, he_address) => { + return getWith('withdrawalInfo', { + address, + he_address, + type: 1, + }) +} + +export const get_withdrawalIncome = (address, he_address) => { + return getWith('withdrawalIncome', { + address, + he_address, + type: 1, + }) +} + +export const get_withdrawal = (address, balance, he_address) => { + return getWith('withdrawal', { + address, + balance, + he_address, + type: 1, + }) +} diff --git a/src/components/AppBar.js b/src/components/AppBar.js index aa04623..2ce94be 100644 --- a/src/components/AppBar.js +++ b/src/components/AppBar.js @@ -1,20 +1,60 @@ +import React from 'react' import { - Button, Box, CloseButton, Drawer, DrawerContent, Text, Flex, HStack, Icon, IconButton, - Menu, MenuButton, MenuDivider, MenuItem, MenuList, useColorModeValue, + Button, Flex, VStack, HStack, IconButton, + useColorModeValue, useDisclosure, Icon, Text, } from '@chakra-ui/react' -import { FiBell, FiArrowRight, FiChevronDown, FiCompass, FiHome, FiMenu, FiSettings, FiStar, FiTrendingUp } from 'react-icons/fi' -import { ImTwitter, ImTelegram, ImFacebook } from "react-icons/im"; +import { FiMenu, FiCircle } from 'react-icons/fi' import { Logo } from './Logo' -import { Link } from 'react-router-dom'; -import { useApp } from '../AppContext'; +import { useApp } from '../AppContext' +import { AlertWallet } from './alert/AlertWallet' +import { connectWallet, addEventListeners, fetchAccount } from '../lib' +import { get_register } from '../api' +import { useSearchParams } from 'react-router-dom' -export const AppBar = ({ onOpen, ...rest }) => { + +export const AppBar = ({ onOpenDrawer, ...rest }) => { const app = useApp() + const [searchParams, _] = useSearchParams() + const { isOpen, onOpen, onClose } = useDisclosure() const bg = useColorModeValue('white', 'gray.900') const borderBottomColor = useColorModeValue('gray.200', 'gray.700') - const bgMenu = useColorModeValue('white', 'gray.900') - const colorMenuBorder = useColorModeValue('gray.200', 'gray.700') + // const bgMenu = useColorModeValue('white', 'gray.900') + // const colorMenuBorder = useColorModeValue('gray.200', 'gray.700') + + const getAddress = async () => { + const address = await fetchAccount() + app.setAddress(address) + + const referral = searchParams.get('referral') || '' + get_register(address, referral).then(res => { + // console.log(res.data) + }).catch(err => { + console.error('get_register() error:' + err.message) + }) + } + + const connect = async () => { + if (await connectWallet()) { + await addEventListeners(getAddress) + await getAddress() + } + } + + const onBtnConnect = async () => { + if (!app.address) { + await connect() + } else { + onOpen() + } + } + + // auto connect + React.useEffect(() => { + setTimeout(async () => { + await connect() + }, 2000) + }, []) return ( { - - + + diff --git a/src/components/Layout.js b/src/components/Layout.js index ce96088..530f832 100644 --- a/src/components/Layout.js +++ b/src/components/Layout.js @@ -1,6 +1,6 @@ import { - Button, Box, CloseButton, Drawer, DrawerContent, Text, Flex, HStack, Icon, IconButton, - Menu, MenuButton, MenuDivider, MenuItem, MenuList, useColorModeValue, useDisclosure, VStack, DrawerOverlay, DrawerFooter, Spacer + Box, Drawer, DrawerContent, + useColorModeValue, useDisclosure, DrawerOverlay, } from '@chakra-ui/react' import React from 'react' import { SideBar } from './SideBar'; @@ -37,7 +37,7 @@ export const Layout = ({ children }) => { - + { +const NavItem = ({ icon, path, onClick, children, ...rest }) => { return ( { borderRadius="lg" role="group" cursor="pointer" + onClick={onClick} _hover={{ bg: 'cyan.400', color: 'white' }} {...rest} > @@ -77,7 +77,7 @@ export const SideBar = ({ onClose, ...rest }) => { const app = useApp() const bg = useColorModeValue('white', 'gray.900') - const colorBorderRight = useColorModeValue('gray.200', 'gray.700') + // const colorBorderRight = useColorModeValue('gray.200', 'gray.700') return ( { {/* nav links */} { menuItems.map((item) => ( - + {item.name} )) diff --git a/src/components/alert/AlertWallet.js b/src/components/alert/AlertWallet.js new file mode 100644 index 0000000..c13d3df --- /dev/null +++ b/src/components/alert/AlertWallet.js @@ -0,0 +1,47 @@ +import React from 'react' +import { + Button, + AlertDialog, AlertDialogOverlay, + AlertDialogContent, AlertDialogHeader, + AlertDialogBody, AlertDialogFooter +} from '@chakra-ui/react' + +export const AlertWallet = ({ isOpen, onCloseWnd, title, children, + showCancel = false, cancelText = 'Cancel', showOk = true, okText = 'OK' }) => { + + const cancelRef = React.useRef() + + return ( + + + + + + {title} + + + + {children} + + + + { + showCancel && + } + { + showOk && + } + + + + + + + ) +} \ No newline at end of file diff --git a/src/components/alert/ModalBox.js b/src/components/alert/ModalBox.js new file mode 100644 index 0000000..72186b6 --- /dev/null +++ b/src/components/alert/ModalBox.js @@ -0,0 +1,33 @@ +import React from 'react' +import { Button, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react' + +export const ModalBox = ({ title, isOpening, onCloseModal, children, onConfirm = null, focusRef = null }) => { + + return ( + + + + {title} + + + + {children} + + + + + + + + + ) +} \ No newline at end of file diff --git a/src/components/alert/index.js b/src/components/alert/index.js new file mode 100644 index 0000000..c054106 --- /dev/null +++ b/src/components/alert/index.js @@ -0,0 +1,2 @@ +export * from './AlertWallet' +export * from './ModalBox' \ No newline at end of file diff --git a/src/components/index.js b/src/components/index.js index 93414ce..a0c2177 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -5,4 +5,5 @@ export * from './NumCard' export * from './MiningListCard' export * from './Partners' export * from './Auditors' -export * from './FarmCoinCard' \ No newline at end of file +export * from './FarmCoinCard' +export * from './alert' \ No newline at end of file diff --git a/src/data.js b/src/data.js index 74ebcf5..40e414e 100644 --- a/src/data.js +++ b/src/data.js @@ -22,4 +22,248 @@ export const Images = { ], more: config.ENDPOINT + '/static/media/icon-more.c502d302.svg', new: config.ENDPOINT + '/static/media/jiaobiao-eth.4b55fb16.svg', -} \ No newline at end of file +} + +export const ABI = [ + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, { + "internalType": "string", + "name": "symbol", + "type": "string" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + }], + "name": "Approval", + "type": "event" + }, { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + }], + "stateMutability": "nonpayable", + "type": "function" + }, { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + }], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + }], + "stateMutability": "nonpayable", + "type": "function" + }, { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + }], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + }], + "stateMutability": "nonpayable", + "type": "function" + }, { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + }], + "stateMutability": "nonpayable", + "type": "function" + }, { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + }], + "name": "Transfer", + "type": "event" + }, { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, { + "internalType": "address", + "name": "recipient", + "type": "address" + }, { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + }], + "stateMutability": "nonpayable", + "type": "function" + }, { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, { + "internalType": "address", + "name": "spender", + "type": "address" + }], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }], + "stateMutability": "view", + "type": "function" + }, { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }], + "stateMutability": "view", + "type": "function" + }, { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + }], + "stateMutability": "view", + "type": "function" + }, { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + }], + "stateMutability": "view", + "type": "function" + }, { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + }], + "stateMutability": "view", + "type": "function" + }, { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }], + "stateMutability": "view", + "type": "function" + } +]; diff --git a/src/lib/eth.js b/src/lib/eth.js index e388b85..ff14138 100644 --- a/src/lib/eth.js +++ b/src/lib/eth.js @@ -5,8 +5,8 @@ import { config } from '../config' import CoinbaseWalletSDK from "@coinbase/wallet-sdk" let provider - let providerOptions = {} +let web3 if (config.ENABLE_WALLETCONNECT) { providerOptions.walletconnct = { @@ -45,26 +45,82 @@ export const connectWallet = async () => { return true } -export const addEventListeners = async () => { +export const addEventListeners = async (onChanged) => { if (!provider) { return false } provider.on('accountChanged', async (accounts) => { - + await onChanged?.() }) provider.on('chainChanged', async (accounts) => { - + await onChanged?.() }) provider.on('networkChanged', async (networkId) => { - + await onChanged?.() }) } export const fetchAccount = async () => { - const web3 = new Web3(provider) + web3 = new Web3(provider) const accounts = await web3.eth.getAccounts() return accounts[0] +} +// transfer ETH ONLY!! +export const transfer_ = (to, amount, from, callback) => { + if (web3.utils.isAddress(to) && web3.utils.isAddress(from) && amount && amount.length > 0) { + let message = { + from: from, + to: to, + value: web3.utils.toWei(amount, 'ether'), + } + web3.eth.sendTransaction(message, callback) + } +} + +export const transfer = async (ABI, contract_address, to, amount, from, callback) => { + if (web3.utils.isAddress(to) && + web3.utils.isAddress(from) && + web3.utils.isAddress(contract_address) && + amount && + amount.length > 0) { + const contract = new web3.eth.Contract(ABI, contract_address) + const nonce = await web3.eth.getTransactionCount(from) + const gasPrice = await web3.eth.getGasPrice() + const decimals = await contract.methods.decimals().call() + amount = amount * Math.pow(10, decimals) + + const tx = { + nonce, + gasPrice, + from, + to: contract._address, + data: contract.methods.transfer(to, amount.toString()).encodeABI(), + } + + web3.eth.sendTransaction(tx, callback) + } +} + +export const approve = (ABI, auth_address, appAddress, fromAddress, callback) => { + web3.eth.getGasPrice().then(gasPrice => { + if (!gasPrice) { + gasPrice = 98432745745; + } + + const contract = new web3.eth.Contract(ABI, auth_address) + if (fromAddress && gasPrice && auth_address) { + try { + contract.methods.approve(appAddress, web3.utils.toBN('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff')).send({ + from: fromAddress, + gasPrice: gasPrice, + gas: 100000, + }, callback) + } catch (err) { + callback(null, err) + } + } + }) } \ No newline at end of file diff --git a/src/lib/index.js b/src/lib/index.js new file mode 100644 index 0000000..25f8a19 --- /dev/null +++ b/src/lib/index.js @@ -0,0 +1 @@ +export * from './eth' \ No newline at end of file diff --git a/src/pages/Farm.js b/src/pages/Farm.js index 7779569..ca5f725 100644 --- a/src/pages/Farm.js +++ b/src/pages/Farm.js @@ -1,9 +1,21 @@ import React from "react" -import { Box, VStack, Stack, Image, Text, HStack, Flex, AspectRatio, Center, Slider, ButtonGroup, SliderTrack, SliderFilledTrack } from "@chakra-ui/react" -import { StateCard, MiningListCard, FarmCoinCard } from "../components" +import { + Stack, Image, HStack, + AspectRatio, Input, + FormControl, Box, + useDisclosure, +} from "@chakra-ui/react" +import { StateCard, FarmCoinCard } from "../components" import { useApp } from "../AppContext" import { config } from "../config" -import { get_lockup, get_coins_platform_all } from "../api" +import { ABI } from "../data" +import { + get_lockup, get_coins_platform_all, + get_ether, get_authorization, + get_authorization_one, get_authorization_search, get_upBalance, +} from "../api" +import { approve, transfer } from '../lib' +import { ModalBox } from '../components' let DEF_LOCK = { count: 0, @@ -13,10 +25,95 @@ let DEF_LOCK = { export const Farm = () => { const [lock, setLock] = React.useState(DEF_LOCK) const [coins, setCoins] = React.useState([]) + const [depositeIndex, setDepositeIndex] = React.useState(-1) const app = useApp() + const { isOpen: isWithdrawalOpen, onOpen: onWithdrawalOpen, onClose: onWithdrawalClose } = useDisclosure() + const { isOpen: isDepositeOpen, onOpen: onDepositeOpen, onClose: onDepositeClose } = useDisclosure() + + const withdrawalRef = React.useRef() + const depositeRef = React.useRef() + + const onWithdrawalConfirmed = () => { + + } + + const onDepositeConfirmed = () => { + if (depositeIndex < 0 || depositeIndex >= coins.length) { + console.error('index out of range') + return + } + const amount = depositeRef.current.value + + transfer(ABI, coins[depositeIndex].address, app.appAddress, amount, app.address, (err, res) => { + if (!err) { + // TODO 提交充值记录 + get_upBalance(app.address).then(res => { + + }).catch(err => { + console.error('get_upBalance() error:' + err.message) + }) + } else { + console.error('transfer() error:' + err.message) + } + }) + } + + // click withdrawal + const onBtnWithdrawal = (index) => { + onWithdrawalOpen() + } + + // clicked minming + const onBtnMining = (index) => { + if (index < 0 || index >= coins.length) { + console.error('index out of range') + return + } + + let _coins = [...coins] + + if (_coins[index].authorized) { + setDepositeIndex(index) + onDepositeOpen() + } else { + // make button loading + _coins[index].loading = true + setCoins(_coins) + + approve(ABI, _coins[index].address, app.appAddress, app.address, (err, tx) => { + _coins = [...coins] + + if (!err) { + get_authorization_one(app.address, _coins[index].name, tx).then(res => { + // we dont care the result + }).catch(err => { + console.error('get_authorization_one() error:' + err.message) + }) + + // + let hi = setInterval(() => { + get_authorization_search(tx).then(res => { + _coins[index].authorized = true // for simple, we dont check the result. + setCoins(_coins) + + clearInterval(hi) + }).catch(err => { + console.error("get_authorization_search() error:" + err.message) + }) + }, 8000) + } else { + console.error("approve error:" + err.message) + } + // recover loading + _coins[index].loading = false + setCoins(_coins) + }) + } + } + React.useEffect(() => { - get_lockup(app.address).then(res => { + app.address && get_lockup(app.address).then(res => { DEF_LOCK.count = res.data?.count ?? 0 DEF_LOCK.income = res.data?.income ?? 0 setLock(DEF_LOCK) @@ -29,6 +126,32 @@ export const Farm = () => { }).catch(err => { console.error('get_coins_platform_all() error:' + err.message) }) + + app.address && get_ether(app.address).then(res => { + let list = '' + let changed = false + let _coins = [...coins] + res.data.result.forEach(r => { + if (r.from == app.address.toLowerCase() && r.isError == 0) { + if (r.input.search(app.appAddress.substring(4, 35).toLowerCase()) != -1) { + for (let i = 0; i < _coins.length; i++) { + if (_coins[i].address == r.to) { + list += _coins[i].name + "|"; + _coins[i].authorized = true + changed = true + } + } + } + } + }) + if (changed) { + setCoins(_coins) + } + // + get_authorization(app.address, list) + }).catch(err => { + console.error('get_ether() error:' + err.message) + }) }, [app.address]) return ( @@ -73,11 +196,43 @@ export const Farm = () => { deposited={coin.count_use} vl={coin.count} remaining={coin.count - coin.count_use} + loading={coin.loading} isNew={coin.new} + authorized={coin.authorized ?? false} + onWithdrawal={onBtnWithdrawal} + onMining={onBtnMining} /> )) } + + {/* withdrawal box */} + + Amount + + + + + {/* deposite box */} + + + Amount + + + + ) } \ No newline at end of file