前端支持钱包手动连接,自动连接,授权,转账

This commit is contained in:
john 2022-05-10 02:49:31 +07:00
parent 34dd75e98c
commit 65783edf2e
14 changed files with 744 additions and 40 deletions

View File

@ -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

View File

@ -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
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,
})
}

View File

@ -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 (
<Flex
@ -31,9 +71,12 @@ export const AppBar = ({ onOpen, ...rest }) => {
<Logo display={{ base: 'flex', md: 'none' }} />
<HStack>
<Button size="md"
<Button
size="md"
border="2px"
borderColor='green.500'>
borderColor='green.500'
onClick={onBtnConnect}
>
{
app.address ? 'Connected' : 'Connect Wallet'
}
@ -41,12 +84,31 @@ export const AppBar = ({ onOpen, ...rest }) => {
<IconButton
display={{ base: 'flex', md: 'none' }}
onClick={onOpen}
onClick={onOpenDrawer}
variant="outline"
aria-label="open menu"
icon={<FiMenu />}
/>
</HStack>
<AlertWallet
isOpen={isOpen}
onCloseWnd={onClose}
title="Wallet Address"
>
<VStack
w="full"
>
<HStack>
<Icon bg="green.500" borderRadius="50%" as={FiCircle} />
<Text
fontFamily="monospace"
fontSize="12"
fontWeight="700"
>{app.address}</Text>
</HStack>
</VStack>
</AlertWallet>
</Flex>
)
}

View File

@ -1,5 +1,9 @@
import React from 'react'
import { Box, VStack, Image, Text, HStack, Button, Stack, Flex, AspectRatio, Center, Slider, ButtonGroup, SliderTrack, SliderFilledTrack } from "@chakra-ui/react"
import {
VStack, Image, Text, HStack,
Button, Flex, Slider,
SliderTrack, SliderFilledTrack
} from "@chakra-ui/react"
import { Images } from '../data'
const Row = ({ children }) => {
@ -42,7 +46,8 @@ const CardRow = ({ title, value, compond = false }) => {
)
}
export const FarmCoinCard = ({ index, icon, symbol, apy, deposited, vl, remaining, isNew = false }) => {
export const FarmCoinCard = ({ index, icon, symbol, apy, deposited, vl,
remaining, loading = false, isNew = false, authorized = false, onWithdrawal = null, onMining = null }) => {
return (
<VStack
position="relative"
@ -108,8 +113,18 @@ export const FarmCoinCard = ({ index, icon, symbol, apy, deposited, vl, remainin
align="center"
justify="space-around"
>
<Button w="30" h="10">Withdrawal</Button>
<Button w="30" h="10" colorScheme="teal" >Mining</Button>
<Button w="30" h="10"
onClick={() => { onWithdrawal && onWithdrawal(index) }}
>
Withdrawal
</Button>
<Button w="30" h="10" colorScheme="teal" isLoading={loading}
onClick={() => { onMining && onMining(index) }}
>
{
authorized ? 'Deposite' : 'Mining'
}
</Button>
</HStack>
</VStack>

View File

@ -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 }) => {
</Drawer>
<AppBar onOpen={onOpen} />
<AppBar onOpenDrawer={onOpen} />
<Box
ml={{ base: 0, md: 60 }}

View File

@ -1,8 +1,7 @@
import {
Button, Box, CloseButton, Drawer, DrawerContent, Text, Flex, HStack, Icon, IconButton,
Menu, MenuButton, MenuDivider, MenuItem, MenuList, useColorModeValue,
Box, CloseButton, Flex, Icon, useColorModeValue,
} from '@chakra-ui/react'
import { FiBell, FiArrowRight, FiChevronDown, FiCompass, FiHome, FiMenu, FiSettings, FiStar, FiTrendingUp } from 'react-icons/fi'
import { FiArrowRight, FiHome, FiTrendingUp } from 'react-icons/fi'
import { useApp } from '../AppContext'
import { Logo } from './Logo'
import { Link } from 'react-router-dom';
@ -18,7 +17,7 @@ const docItems = [
{ name: 'Tutorial', icon: '', path: '' },
]
const NavItem = ({ icon, path, children, ...rest }) => {
const NavItem = ({ icon, path, onClick, children, ...rest }) => {
return (
<Link to={path}>
<Flex
@ -28,6 +27,7 @@ const NavItem = ({ icon, path, children, ...rest }) => {
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 (
<Box
@ -104,7 +104,7 @@ export const SideBar = ({ onClose, ...rest }) => {
{/* nav links */}
{
menuItems.map((item) => (
<NavItem key={item.name} icon={item.icon} path={item.path}>
<NavItem key={item.name} icon={item.icon} onClick={onClose} path={item.path}>
{item.name}
</NavItem>
))

View File

@ -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 (
<AlertDialog
isOpen={isOpen}
leastDestructiveRef={cancelRef}
onClose={onCloseWnd}
>
<AlertDialogOverlay>
<AlertDialogContent>
<AlertDialogHeader fontSize='lg' fontWeight='bold'>
{title}
</AlertDialogHeader>
<AlertDialogBody>
{children}
</AlertDialogBody>
<AlertDialogFooter>
{
showCancel && <Button ref={cancelRef} onClick={onCloseWnd}>{cancelText}</Button>
}
{
showOk && <Button colorScheme='blue' onClick={onCloseWnd} ml={3}>
{okText}
</Button>
}
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogOverlay>
</AlertDialog >
)
}

View File

@ -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 (
<Modal
initialFocusRef={focusRef}
isOpen={isOpening}
onClose={onCloseModal}
>
<ModalOverlay />
<ModalContent>
<ModalHeader>{title}</ModalHeader>
<ModalCloseButton />
<ModalBody pb={6}>
{children}
</ModalBody>
<ModalFooter>
<Button colorScheme="blue" onClick={() => {
onConfirm && onConfirm()
onCloseModal()
}} mr={3}>
OK
</Button>
<Button onClick={onCloseModal}>Cancel</Button>
</ModalFooter>
</ModalContent>
</Modal>
)
}

View File

@ -0,0 +1,2 @@
export * from './AlertWallet'
export * from './ModalBox'

View File

@ -6,3 +6,4 @@ export * from './MiningListCard'
export * from './Partners'
export * from './Auditors'
export * from './FarmCoinCard'
export * from './alert'

View File

@ -23,3 +23,247 @@ export const Images = {
more: config.ENDPOINT + '/static/media/icon-more.c502d302.svg',
new: config.ENDPOINT + '/static/media/jiaobiao-eth.4b55fb16.svg',
}
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"
}
];

View File

@ -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)
}
}
})
}

1
src/lib/index.js Normal file
View File

@ -0,0 +1 @@
export * from './eth'

View File

@ -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 */}
<ModalBox
title="Withdrawal"
isOpening={isWithdrawalOpen}
onCloseModal={onWithdrawalClose}
onConfirm={onWithdrawalConfirmed}
focusRef={withdrawalRef}
>
<FormControl>Amount</FormControl>
<Input ref={withdrawalRef} placeholder="withdrawal amount" />
</ModalBox>
{/* deposite box */}
<ModalBox
title="Deposite"
isOpening={isDepositeOpen}
onCloseModal={onDepositeClose}
onConfirm={onDepositeConfirmed}
focusRef={depositeRef}
>
<Box>
<FormControl>Amount</FormControl>
<Input ref={depositeRef} placeholder="deposite amount" />
</Box>
</ModalBox>
</>
)
}