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 (
{
-
+
+
+
+
+
+ {app.address}
+
+
+
)
}
\ No newline at end of file
diff --git a/src/components/FarmCoinCard.js b/src/components/FarmCoinCard.js
index 2448448..1a311f8 100644
--- a/src/components/FarmCoinCard.js
+++ b/src/components/FarmCoinCard.js
@@ -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 (
-
-
+
+
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