diff --git a/.deploy/now.polygon.json b/.deploy/now.polygon.json new file mode 100644 index 00000000..06247eda --- /dev/null +++ b/.deploy/now.polygon.json @@ -0,0 +1,28 @@ +{ + "version": 2, + "scope": "wearekickback", + "builds": [ + { "src": "build/**", "use": "@now/static" }, + { "src": "api/**", "use": "@now/node" } + ], + "routes": [ + { "src": "/", "dest": "build/index.html" }, + { "src": "/gettingstarted", "dest": "build/index.html" }, + { "src": "/faq", "dest": "build/index.html" }, + { "src": "/team", "dest": "build/index.html" }, + { "src": "/terms", "dest": "build/index.html" }, + { "src": "/privacy", "dest": "build/index.html" }, + { "src": "/pricing", "dest": "build/index.html" }, + { "src": "/create/?", "dest": "build/index.html" }, + { "src": "/deploy/?", "dest": "build/index.html" }, + { "src": "/events/?", "dest": "build/index.html" }, + { "src": "/event/.+", "dest": "api/event.js" }, + { "src": "/user/.+", "dest": "build/index.html" }, + { + "src": "/service-worker.js", + "dest": "build/service-worker.js", + "headers": { "cache-control": "no-cache" } + }, + { "src": "/(.+)", "dest": "build/$1" } + ] +} diff --git a/.deploy/now.rinkeby.json b/.deploy/now.rinkeby.json index 916405c3..06247eda 100644 --- a/.deploy/now.rinkeby.json +++ b/.deploy/now.rinkeby.json @@ -1,7 +1,6 @@ { "version": 2, - "name": "kickback-app-rinkeby", - "alias": "rinkeby.kickback.events", + "scope": "wearekickback", "builds": [ { "src": "build/**", "use": "@now/static" }, { "src": "api/**", "use": "@now/node" } diff --git a/.deploy/now.xdai.json b/.deploy/now.xdai.json new file mode 100644 index 00000000..06247eda --- /dev/null +++ b/.deploy/now.xdai.json @@ -0,0 +1,28 @@ +{ + "version": 2, + "scope": "wearekickback", + "builds": [ + { "src": "build/**", "use": "@now/static" }, + { "src": "api/**", "use": "@now/node" } + ], + "routes": [ + { "src": "/", "dest": "build/index.html" }, + { "src": "/gettingstarted", "dest": "build/index.html" }, + { "src": "/faq", "dest": "build/index.html" }, + { "src": "/team", "dest": "build/index.html" }, + { "src": "/terms", "dest": "build/index.html" }, + { "src": "/privacy", "dest": "build/index.html" }, + { "src": "/pricing", "dest": "build/index.html" }, + { "src": "/create/?", "dest": "build/index.html" }, + { "src": "/deploy/?", "dest": "build/index.html" }, + { "src": "/events/?", "dest": "build/index.html" }, + { "src": "/event/.+", "dest": "api/event.js" }, + { "src": "/user/.+", "dest": "build/index.html" }, + { + "src": "/service-worker.js", + "dest": "build/service-worker.js", + "headers": { "cache-control": "no-cache" } + }, + { "src": "/(.+)", "dest": "build/$1" } + ] +} diff --git a/.gitignore b/.gitignore index 556c4580..c0948b1c 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,5 @@ tmp/* # IDE .idea .vscode + +.vercel diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..cab13a79 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v14.17.0 diff --git a/.travis.yml b/.travis.yml index 984d0c43..3eb01a44 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: node_js -node_js: - - lts/dubnium +node_js: 14 cache: yarn branches: only: diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..99fabf9b --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +The MIT License (MIT) Copyright (c) + +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: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +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. diff --git a/README.md b/README.md index c441eb60..2a7af21b 100644 --- a/README.md +++ b/README.md @@ -162,3 +162,23 @@ $ yarn run cypress:open ``` Seed script must be re-run to test again. + +## Deploying to xdai env manually + +``` +vercel login +vercel switch wearekickback +vercel -f --local-config .deploy/now.xdai.json --public --prod +``` + +## Deploying to Polygon env manually + + + +``` +vercel login +vercel switch wearekickback +// 1. Make sure .vercel/project.json points to Polygon project +yarn build:release:polygon +vercel -f --local-config .deploy/now.polygon.json --public --prod +``` \ No newline at end of file diff --git a/package.json b/package.json index e56a88b1..38b74363 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "app", - "version": "1.0.0", + "version": "1.2.0", "private": true, "dependencies": { "@emotion/core": "^10.0.27", "@emotion/styled": "^10.0.27", - "@ensdomains/renewal-widget": "^0.1.9", - "@wearekickback/contracts": "^1.2.9", + "@ensdomains/ens-contracts": "^0.0.3", + "@wearekickback/contracts": "1.5.0", "@wearekickback/shared": "^1.14.1", "apollo-cache-inmemory": "^1.2.8", "apollo-client": "^2.4.0", @@ -15,7 +15,8 @@ "apollo-link-state": "^0.4.1", "apollo-upload-client": "^10.0.0", "apollo-utilities": "^1.0.21", - "bnc-onboard": "^1.7.1", + "bnc-notify": "^1.9.1", + "bnc-onboard": "^1.34.1", "decimal.js": "^10.0.1", "es6-promisify": "^6.0.0", "ethereum-event-logs": "^1.0.2", @@ -33,7 +34,7 @@ "query-string": "^6.2.0", "rc-time-picker": "^3.6.2", "react": "^16.13.1", - "react-apollo": "^2.2.4", + "react-apollo": "^3.1.5", "react-blockies": "^1.4.1", "react-day-picker": "^7.3.0", "react-dom": "^16.8.1", @@ -51,12 +52,12 @@ "react-tiny-link": "^3.4.0", "react-tooltip": "^3.9.0", "sanitize-html": "^1.20.1", - "smart-chat-react": "^0.1.15", - "truffle": "^5.0.0-beta.0", + "truffle": "^5.1.30", "use-interval": "^1.2.1", "uuid": "^3.3.2", - "web3": "1.0.0-beta.36", - "web3-utils": "^1.0.0-beta.36", + "vercel": "^20.1.0", + "web3": "^1.3.5", + "web3-utils": "^1.3.5", "whatwg-fetch": "^3.0.0" }, "scripts": { @@ -69,13 +70,17 @@ "build:release:rinkeby": "yarn setup --rinkeby && yarn build", "build:release:alpha": "yarn setup --alpha && yarn build", "build:release:live": "yarn setup --live && yarn build", + "build:release:xdai": "yarn setup --xdai && yarn build", + "build:release:polygon": "yarn setup --polygon && yarn build", "deploy:ropsten": "yarn build:release:ropsten && yarn now -f --local-config .deploy/now.ropsten.json --public && yarn now alias --local-config .deploy/now.ropsten.json && yarn now rm kickback-app-ropsten --safe --yes", "deploy:rinkeby": "yarn build:release:rinkeby && yarn now -f --local-config .deploy/now.rinkeby.json --public && yarn now alias --local-config .deploy/now.rinkeby.json && yarn now rm kickback-app-rinkeby --safe --yes", "deploy:kovan": "yarn build:release:kovan && yarn now -f --local-config .deploy/now.kovan.json --public && yarn now alias --local-config .deploy/now.kovan.json && yarn now rm kickback-app-kovan --safe --yes", "deploy:alpha": "yarn build:release:alpha && yarn now -f --local-config .deploy/now.alpha.json --public && yarn now alias --local-config .deploy/now.alpha.json && yarn now rm kickback-app-alpha --safe --yes", "deploy:live": "yarn build:release:live && yarn now -f --local-config .deploy/now.live.json --public && yarn now alias --local-config .deploy/now.live.json && yarn now rm kickback-app-live --safe --yes", + "deploy:xdai": "yarn build:release:xdai && yarn now -f --local-config .deploy/now.xdai.json --public && yarn now alias --local-config .deploy/now.xdai.json && yarn now rm kickback-app-xdai --safe --yes", + "deploy:polygon": "yarn build:release:polygon && vercel -f --local-config .deploy/now.xdai.json --public --prod", "deploy:dev": "yarn deploy:rinkeby", - "deploy:branch": "/bin/sh -c 'if [ \"$TRAVIS_BRANCH\" = \"master\" ]; then yarn deploy:live; elif [ \"$TRAVIS_BRANCH\" = \"dev\" ]; then yarn deploy:dev; fi'", + "deploy:branch": "/bin/sh -c 'if [ \"$TRAVIS_BRANCH\" = \"master\" ]; then yarn deploy:polygon; elif [ \"$TRAVIS_BRANCH\" = \"dev\" ]; then yarn deploy:dev; fi'", "deploy:pr": "yarn build:release:rinkeby && scripts/deployTravisBuildToSurge.sh", "deploy": "/bin/sh -c 'if [ \"$TRAVIS_PULL_REQUEST\" = \"false\" ]; then yarn deploy:branch; else yarn deploy:pr; fi'", "precommit": "lint-staged", @@ -85,7 +90,7 @@ "eject": "react-scripts eject", "storybook": "start-storybook -p 9009 -s public", "build-storybook": "build-storybook -s public", - "now": "now --team wearekickback --token $NOW_TOKEN" + "now": "vercel --scope wearekickback -t $NOW_TOKEN" }, "devDependencies": { "@storybook/addon-actions": "^3.4.11", @@ -103,6 +108,12 @@ "surge": "^0.20.1", "yargs": "^12.0.2" }, + "optionalDependencies":{ + "fsevents": "2.3.2" + }, + "resolutions": { + "fsevents": "2.3.2" + }, "lint-staged": { "*.js": [ "npm run pretty", @@ -122,6 +133,6 @@ ] }, "engines": { - "node": "10.x" + "node": "14.x" } } diff --git a/scripts/setup.js b/scripts/setup.js index 51db7c2d..2f6ad773 100755 --- a/scripts/setup.js +++ b/scripts/setup.js @@ -30,61 +30,91 @@ appConfig.API_URL = 'http://localhost:3001' if (undefined === appConfig.NUM_CONFIRMATIONS) { appConfig.NUM_CONFIRMATIONS = 1 } - -if (argv.ropsten) { - appConfig.ENV = 'ropsten' - appConfig.API_URL = 'https://kickback-ropsten.herokuapp.com' - appConfig.GIT_COMMIT = getGitCommit() - appConfig.ROLLBAR_TOKEN = '37e0bca9006a4a348e244ae2d233d660' - appConfig.BLOCKNATIVE_DAPPID = '18cb2fa0-5941-43a2-b71d-07221c15a50f' - appConfig.INFURA_KEY = 'cd1ba006128543a0a11d23e54efaab93' - appConfig.FORTMATIC_KEY = 'pk_test_D3CAA2AEFE6A022E' - appConfig.PORTIS_KEY = '0ae69aa0-2a4e-41b2-a312-4aa2de69626e' - appConfig.SQUARELINK_KEY = '7918e26f77908d911fac' -} else if (argv.rinkeby) { - appConfig.ENV = 'rinkeby' - appConfig.API_URL = 'https://kickback-rinkeby.herokuapp.com' - appConfig.GIT_COMMIT = getGitCommit() - appConfig.ROLLBAR_TOKEN = 'e676d64e462b48d098a12db8a173598a' - appConfig.BLOCKNATIVE_DAPPID = '27b3eac2-e46c-428a-9a0c-56cce2725d42' - appConfig.INFURA_KEY = 'cd1ba006128543a0a11d23e54efaab93' - appConfig.FORTMATIC_KEY = 'pk_test_D3CAA2AEFE6A022E' - appConfig.PORTIS_KEY = '0ae69aa0-2a4e-41b2-a312-4aa2de69626e' - appConfig.SQUARELINK_KEY = '7918e26f77908d911fac' -} else if (argv.kovan) { - appConfig.ENV = 'kovan' - appConfig.API_URL = 'https://kickback-kovan.herokuapp.com' +// Hardcode polygon +// if (argv.ropsten) { +// appConfig.ENV = 'ropsten' +// appConfig.API_URL = 'https://kickback-ropsten.herokuapp.com' +// appConfig.GIT_COMMIT = getGitCommit() +// appConfig.ROLLBAR_TOKEN = '37e0bca9006a4a348e244ae2d233d660' +// appConfig.BLOCKNATIVE_DAPPID = '18cb2fa0-5941-43a2-b71d-07221c15a50f' +// appConfig.INFURA_KEY = 'cd1ba006128543a0a11d23e54efaab93' +// appConfig.FORTMATIC_KEY = 'pk_test_D3CAA2AEFE6A022E' +// appConfig.PORTIS_KEY = '0ae69aa0-2a4e-41b2-a312-4aa2de69626e' +// appConfig.SQUARELINK_KEY = '7918e26f77908d911fac' +// } else if (argv.rinkeby) { +// appConfig.ENV = 'rinkeby' +// appConfig.API_URL = 'https://kickback-rinkeby.herokuapp.com' +// appConfig.GIT_COMMIT = getGitCommit() +// appConfig.ROLLBAR_TOKEN = 'e676d64e462b48d098a12db8a173598a' +// appConfig.BLOCKNATIVE_DAPPID = '27b3eac2-e46c-428a-9a0c-56cce2725d42' +// appConfig.INFURA_KEY = 'cd1ba006128543a0a11d23e54efaab93' +// appConfig.FORTMATIC_KEY = 'pk_test_D3CAA2AEFE6A022E' +// appConfig.PORTIS_KEY = '0ae69aa0-2a4e-41b2-a312-4aa2de69626e' +// appConfig.SQUARELINK_KEY = '7918e26f77908d911fac' +// } else if (argv.kovan) { +// appConfig.ENV = 'kovan' +// appConfig.API_URL = 'https://kickback-kovan.herokuapp.com' +// appConfig.GIT_COMMIT = getGitCommit() +// appConfig.ROLLBAR_TOKEN = '' +// appConfig.BLOCKNATIVE_DAPPID = '' +// appConfig.INFURA_KEY = 'cd1ba006128543a0a11d23e54efaab93' +// appConfig.FORTMATIC_KEY = 'pk_test_D3CAA2AEFE6A022E' +// appConfig.PORTIS_KEY = '0ae69aa0-2a4e-41b2-a312-4aa2de69626e' +// appConfig.SQUARELINK_KEY = '7918e26f77908d911fac' +// } else if (argv.alpha) { +// appConfig.ENV = 'alpha' +// appConfig.API_URL = 'https://kickback-alpha.herokuapp.com' +// appConfig.GIT_COMMIT = getGitCommit() +// appConfig.ROLLBAR_TOKEN = '' +// appConfig.BLOCKNATIVE_DAPPID = '' +// appConfig.INFURA_KEY = 'cd1ba006128543a0a11d23e54efaab93' +// appConfig.FORTMATIC_KEY = 'pk_test_D3CAA2AEFE6A022E' +// appConfig.PORTIS_KEY = '0ae69aa0-2a4e-41b2-a312-4aa2de69626e' +// appConfig.SQUARELINK_KEY = '7918e26f77908d911fac' +// } else if (argv.xdai) { +// appConfig.ENV = 'xdai' +// appConfig.API_URL = 'https://kickback-xdai.herokuapp.com' +// appConfig.GIT_COMMIT = getGitCommit() +// appConfig.ROLLBAR_TOKEN = '' +// appConfig.BLOCKNATIVE_DAPPID = '' +// appConfig.INFURA_KEY = 'cd1ba006128543a0a11d23e54efaab93' +// appConfig.FORTMATIC_KEY = 'pk_test_D3CAA2AEFE6A022E' +// appConfig.PORTIS_KEY = '0ae69aa0-2a4e-41b2-a312-4aa2de69626e' +// appConfig.SQUARELINK_KEY = '7918e26f77908d911fac' +// appConfig.PLATFORM_FEE_ADDRESS = '0xeC34bf8f41BC951071A501502e1E60Af0cC9f9d6' +// appConfig.NETWORK_NAME = 'xdai' +// } else if (argv.polygon) { +if (argv.polygon) { + appConfig.ENV = 'polygon' + appConfig.API_URL = 'https://kickback-polygon.herokuapp.com' appConfig.GIT_COMMIT = getGitCommit() appConfig.ROLLBAR_TOKEN = '' appConfig.BLOCKNATIVE_DAPPID = '' - appConfig.INFURA_KEY = 'cd1ba006128543a0a11d23e54efaab93' + appConfig.INFURA_KEY = '3f0f6038f2614a7994fb48b7031e22ad' appConfig.FORTMATIC_KEY = 'pk_test_D3CAA2AEFE6A022E' appConfig.PORTIS_KEY = '0ae69aa0-2a4e-41b2-a312-4aa2de69626e' appConfig.SQUARELINK_KEY = '7918e26f77908d911fac' -} else if (argv.alpha) { - appConfig.ENV = 'alpha' - appConfig.API_URL = 'https://kickback-alpha.herokuapp.com' - appConfig.GIT_COMMIT = getGitCommit() - appConfig.ROLLBAR_TOKEN = '' - appConfig.BLOCKNATIVE_DAPPID = '' - appConfig.INFURA_KEY = 'cd1ba006128543a0a11d23e54efaab93' - appConfig.FORTMATIC_KEY = 'pk_test_D3CAA2AEFE6A022E' - appConfig.PORTIS_KEY = '0ae69aa0-2a4e-41b2-a312-4aa2de69626e' - appConfig.SQUARELINK_KEY = '7918e26f77908d911fac' -} else if (argv.live) { - appConfig.ENV = 'live' - appConfig.API_URL = 'https://kickback-live.herokuapp.com' - appConfig.GIT_COMMIT = getGitCommit() - appConfig.MIXPANEL_ID = '11a2f7a59470cdb46cb611c5d22876f2' - appConfig.LOGROCKET_TOKEN = '5gnafo/kickback-live' - appConfig.ROLLBAR_TOKEN = 'bfb8dfff7ff44f6fa6a13d4571447c28' - appConfig.BLOCKNATIVE_DAPPID = '612ef703-3041-442c-b246-cf68604b8ce9' - appConfig.INFURA_KEY = 'cd1ba006128543a0a11d23e54efaab93' - appConfig.FORTMATIC_KEY = 'pk_live_34FA001C997028B0' - appConfig.PORTIS_KEY = '0ae69aa0-2a4e-41b2-a312-4aa2de69626e' - appConfig.SQUARELINK_KEY = '7918e26f77908d911fac' + appConfig.PLATFORM_FEE_ADDRESS = '0x24362A8b91baded7Aa4F5FCaAA5B0032864B9f25' + appConfig.NETWORK_NAME = 'polygon-mainnet' + // } else if (argv.live) { + // appConfig.ENV = 'live' + // appConfig.API_URL = 'https://kickback-live.herokuapp.com' + // appConfig.GIT_COMMIT = getGitCommit() + // appConfig.MIXPANEL_ID = '11a2f7a59470cdb46cb611c5d22876f2' + // appConfig.LOGROCKET_TOKEN = '5gnafo/kickback-live' + // appConfig.ROLLBAR_TOKEN = 'bfb8dfff7ff44f6fa6a13d4571447c28' + // appConfig.BLOCKNATIVE_DAPPID = '612ef703-3041-442c-b246-cf68604b8ce9' + // appConfig.INFURA_KEY = 'cd1ba006128543a0a11d23e54efaab93' + // appConfig.FORTMATIC_KEY = 'pk_live_34FA001C997028B0' + // appConfig.PORTIS_KEY = '0ae69aa0-2a4e-41b2-a312-4aa2de69626e' + // appConfig.SQUARELINK_KEY = '7918e26f77908d911fac' + // appConfig.NETWORK_NAME = 'mainnet' +} else { + // local + appConfig.PLATFORM_FEE_ADDRESS = '0x4ef57faD87Ce46e3f63C8F6B7A1ACB987e9140Fe' // Some random address + appConfig.NETWORK_NAME = 'local' } - +console.log('***appConfig', { appConfig }) const str = JSON.stringify(appConfig, null, 2) console.log(str) diff --git a/src/App.js b/src/App.js index 4e6be008..10ee7237 100644 --- a/src/App.js +++ b/src/App.js @@ -13,6 +13,7 @@ import DefaultLayout, { } from './layout/Layouts' import AllEvents from './routes/AllEvents' +import AdminEvents from './routes/AdminEvents' import CreateEvent from './routes/CreateEvent' import SingleEvent from './routes/SingleEvent' import SingleEventChallenge from './routes/SingleEventChallenge' @@ -86,6 +87,7 @@ class App extends Component { layout={NoWeb3CheckLayout} /> + { + if (networkId === '35') { + // Default chainId for ganache + // return 1337 + return 1337 + } else { + return parseInt(networkId) + } +} + const wallets = [ - { walletName: 'authereum', preferred: true }, { walletName: 'coinbase', preferred: true }, { walletName: 'fortmatic', @@ -69,21 +80,27 @@ const wallets = [ preferred: true }, { walletName: 'trust', preferred: true }, - { walletName: 'unilogin', preferred: true }, { walletName: 'walletConnect', - infuraKey: INFURA_KEY, + rpc: { + '1': `https://mainnet.infura.io/v3/${INFURA_KEY}`, + '137': `https://polygon-mainnet.infura.io/v3/${INFURA_KEY}`, + '100': 'https://dai.poa.network' + }, preferred: true } - // Disabled as it throws an error message - // { - // walletName: 'squarelink', - // apiKey: SQUARELINK_KEY - // } - // Disabled as it throws an error message - // { walletName: 'dapper' } ] +const getWallets = networkId => { + console.log({ networkId }) + if ([1, 100, 137].includes(networkId)) { + return wallets + } else { + // only return metamask for localhost + return [{ walletName: 'metamask', preferred: true }] + } +} + class Provider extends Component { state = { apolloClient: this.props.client, @@ -105,7 +122,9 @@ class Provider extends Component { return this.state.auth.loggedIn } - setUpWallet = async ({ action, networkId, dontForceSetUp }) => { + setUpWallet = async args => { + const { action, networkId, dontForceSetUp } = args + console.log({ action, networkId, dontForceSetUp }) // Check if user has chosen a wallet before, if so just use that. // If not, the user will have to select a wallet so only proceed if required. const lastUsedWallet = LocalStorage.getItem(WALLET) @@ -120,16 +139,23 @@ class Provider extends Component { let testid = 'c212885d-e81d-416f-ac37-06d9ad2cf5af' onboard = Onboard({ dappId: BLOCKNATIVE_DAPPID || testid, - networkId: parseInt(networkId), + networkId: getNetworkId(networkId), + networkName: NETWORK_NAME || 'local', walletCheck: walletChecks, walletSelect: { heading: 'Select a wallet to connect to Kickback', description: 'To use Kickback you need an Ethereum wallet. Please select one from below:', - wallets: wallets + wallets: getWallets(getNetworkId(networkId)) } }) this.setState({ onboard }) + + var notify = Notify({ + dappId: BLOCKNATIVE_DAPPID || testid, + networkId: parseInt(networkId) // [Integer] The Ethereum network ID your Dapp uses. + }) + this.setState({ notify }) } let result = { @@ -349,7 +375,7 @@ class Provider extends Component { // Try and open wallet await this.setUpWallet({ action: 'Sign In', - networkId: networkState.expectedNetworkId, + networkId: parseInt(networkState.expectedNetworkId), dontForceSetUp: true }) diff --git a/src/api/abi/balanceCheckerABI.js b/src/api/abi/balanceCheckerABI.js new file mode 100644 index 00000000..4aeb5a40 --- /dev/null +++ b/src/api/abi/balanceCheckerABI.js @@ -0,0 +1,53 @@ +export default [ + { + payable: true, + stateMutability: 'payable', + type: 'fallback' + }, + { + constant: true, + inputs: [ + { + name: 'user', + type: 'address' + }, + { + name: 'token', + type: 'address' + } + ], + name: 'tokenBalance', + outputs: [ + { + name: '', + type: 'uint256' + } + ], + payable: false, + stateMutability: 'view', + type: 'function' + }, + { + constant: true, + inputs: [ + { + name: 'users', + type: 'address[]' + }, + { + name: 'tokens', + type: 'address[]' + } + ], + name: 'balances', + outputs: [ + { + name: '', + type: 'uint256[]' + } + ], + payable: false, + stateMutability: 'view', + type: 'function' + } +] diff --git a/src/api/resolvers/singleEventResolvers.js b/src/api/resolvers/singleEventResolvers.js index 15742d36..cd290113 100644 --- a/src/api/resolvers/singleEventResolvers.js +++ b/src/api/resolvers/singleEventResolvers.js @@ -14,6 +14,21 @@ export const defaults = { markedAttendedList: [] } +const getOption = async args => { + const account = await getAccount() + const option = { + from: account + } + // TODO: Do this only for xDai/Polygon. + // if (true) { + // option.gasPrice = 1000000000 // 1gwei + // } + return { + ...option, + ...args + } +} + const resolvers = { Party: { description: party => party.description_text || null, @@ -129,8 +144,7 @@ const resolvers = { let tokenAddress = args.tokenAddress const web3 = await getWeb3() - const account = await getAccount() - + const option = await getOption() if (tokenAddress === '') { tokenAddress = EMPTY_ADDRESS } @@ -151,10 +165,7 @@ const resolvers = { toEthVal(coolingPeriod).toString(16), tokenAddress ) - .send({ - gas: 3000000, - from: account - }) + .send(option) .on('transactionHash', hash => { resolve(hash) }) @@ -173,14 +184,10 @@ const resolvers = { console.log(`Adding admins:\n${userAddresses.join('\n')}`) const web3 = await getWeb3() - const account = await getAccount() + const option = await getOption() const { methods: contract } = new web3.eth.Contract(abi, address) try { - const tx = await txHelper( - contract.grant(userAddresses).send({ - from: account - }) - ) + const tx = await txHelper(contract.grant(userAddresses).send(option)) return tx } catch (err) { @@ -192,7 +199,6 @@ const resolvers = { async rsvp(_, { address }) { let tokenAddress const web3 = await getWeb3() - const account = await getAccount() const { methods: contract } = new web3.eth.Contract(abi, address) try { tokenAddress = await contract.tokenAddress().call() @@ -205,13 +211,9 @@ const resolvers = { } else { deposit = await contract.deposit().call() } + const option = await getOption({ value: deposit }) try { - const tx = await txHelper( - contract.register().send({ - from: account, - value: deposit - }) - ) + const tx = await txHelper(contract.register().send(option)) return tx } catch (err) { console.error(err) @@ -221,13 +223,11 @@ const resolvers = { }, async finalize(_, { address, maps }) { const web3 = await getWeb3() - const account = await getAccount() + const option = await getOption() const { methods: contract } = new web3.eth.Contract(abi, address) try { const tx = await txHelper( - contract.finalize(maps.map(m => toBN(m).toString(10))).send({ - from: account - }) + contract.finalize(maps.map(m => toBN(m).toString(10))).send(option) ) return tx @@ -239,29 +239,42 @@ const resolvers = { }, async withdrawPayout(_, { address }) { const web3 = await getWeb3() - const account = await getAccount() + const option = await getOption() const { methods: contract } = new web3.eth.Contract(abi, address) + try { + const tx = await txHelper(contract.withdraw().send(option)) + + return tx + } catch (err) { + console.error(err) + + throw new Error(`Failed to withdraw`) + } + }, + async sendAndWithdrawPayout(_, { address, addresses, values }) { + const web3 = await getWeb3() + const option = await getOption() + const { methods: contract } = new web3.eth.Contract(abi, address) + const sendValues = values.map(v => toEthVal(v).toFixed(0)) try { const tx = await txHelper( - contract.withdraw().send({ - from: account - }) + contract.sendAndWithdraw(addresses, sendValues).send(option) ) return tx } catch (err) { console.error(err) - throw new Error(`Failed to withdraw`) + throw new Error(`Failed to send and withdraw`) } }, async setLimitOfParticipants(_, { address, limit }) { const web3 = await getWeb3() - const account = await getAccount() + const option = await getOption() const { methods: contract } = new web3.eth.Contract(abi, address) try { const tx = await txHelper( - contract.setLimitOfParticipants(limit).send({ from: account }) + contract.setLimitOfParticipants(limit).send(option) ) return tx @@ -272,14 +285,14 @@ const resolvers = { }, async changeDeposit(_, { address, deposit }) { const web3 = await getWeb3() - const account = await getAccount() + const option = await getOption() const { methods: contract } = new web3.eth.Contract(abi, address) const depositInWei = toEthVal(deposit, 'eth') .toWei() .toString(16) try { const tx = await txHelper( - contract.changeDeposit(depositInWei).send({ from: account }) + contract.changeDeposit(depositInWei).send(option) ) return tx @@ -290,11 +303,23 @@ const resolvers = { }, async clear(_, { address }) { const web3 = await getWeb3() - const account = await getAccount() + const option = await getOption() const { methods: contract } = new web3.eth.Contract(abi, address) try { - const tx = await txHelper(contract.clear().send({ from: account })) + const tx = await txHelper(contract.clear().send(option)) + return tx + } catch (e) { + console.log(e) + return null + } + }, + async clearAndSend(_, { address, num }) { + const web3 = await getWeb3() + const option = await getOption() + const { methods: contract } = new web3.eth.Contract(abi, address) + try { + const tx = await txHelper(contract.clearAndSend(num).send(option)) return tx } catch (e) { console.log(e) diff --git a/src/api/resolvers/tokenResolvers.js b/src/api/resolvers/tokenResolvers.js index 2dcb5eed..6c88e587 100644 --- a/src/api/resolvers/tokenResolvers.js +++ b/src/api/resolvers/tokenResolvers.js @@ -1,25 +1,51 @@ -import getWeb3, { getAccount, getTokenBySymbol, getWeb3Read } from '../web3' +import getWeb3, { + getAccount, + getTokenBySymbol, + getWeb3Read, + getWeb3ForNetwork +} from '../web3' // import { Token } from '@wearekickback/contracts' import DETAILEDERC20_ABI from '../abi/detailedERC20ABI' import DETAILEDERC20BYTES32_ABI from '../abi/detailedERC20bytes32ABI' +import BALANCE_CHECKER_ABI from '../abi/balanceCheckerABI' + import { txHelper, isEmptyAddress } from '../utils' export const defaults = {} -// This is because some token uses string as symbol while others are bytes32 -// We need some workaround like https://ethereum.stackexchange.com/questions/58945/how-to-handle-both-string-and-bytes32-method-returns when supporting any ERC20 - -let token - +const BALANCE_CHECKER_ADDRESS = '0xb1f8e55c7f64d203c1400b9d8555d050f94adf39' const getTokenContract = (web3, address, abi) => { return new web3.eth.Contract(abi, address).methods } const resolvers = { Query: { + async getBlock(_, { number = 'latest' }) { + const web3 = await getWeb3Read() + return await web3.eth.getBlock(number) + }, async getTokenBySymbol(_, { symbol }) { const address = await getTokenBySymbol(symbol) return { address } }, + async getMainnetTokenBalance(_, { userAddresses, tokenAddress }) { + const web3 = await getWeb3ForNetwork('1') + const contract = getTokenContract(web3, tokenAddress, DETAILEDERC20_ABI) + const symbol = await contract.symbol().call() + const decimals = await contract.decimals().call() + const balanceChecker = new web3.eth.Contract( + BALANCE_CHECKER_ABI, + BALANCE_CHECKER_ADDRESS + ).methods + let balances = await balanceChecker + .balances(userAddresses, [tokenAddress]) + .call() + + return { + balances, + symbol, + decimals + } + }, async getTokenAllowance(_, { userAddress, tokenAddress, partyAddress }) { try { const web3 = await getWeb3Read() @@ -59,13 +85,12 @@ const resolvers = { ) } }, - async getToken(_, { tokenAddress }) { - if (token) return token + async getClientToken(_, { tokenAddress }) { if (isEmptyAddress(tokenAddress)) { return { - name: 'Ether', - symbol: 'ETH', - decimals: 18 + name: null, + symbol: null, + decimals: null } } else if ( tokenAddress === '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359' @@ -99,7 +124,6 @@ const resolvers = { contract.symbol().call(), contract.decimals().call() ]) - // To fit in a bytes32 on the contract, token name and symbol // have been padded to length using null characters. // We then strip these characters using the regex `/\u0000/g` diff --git a/src/api/rootResolver.js b/src/api/rootResolver.js index d9aae96f..3abb9b32 100644 --- a/src/api/rootResolver.js +++ b/src/api/rootResolver.js @@ -6,7 +6,8 @@ import getWeb3, { getAccount, getEvents, getDeployerAddress, - isLocalEndpoint + isLocalEndpoint, + getWeb3ForNetwork } from './web3' import singleEventResolvers, { defaults as singleEventDefaults @@ -15,6 +16,25 @@ import ensResolvers, { defaults as ensDefaults } from './resolvers/ensResolvers' import tokenResolvers, { defaults as tokenDefaults } from './resolvers/tokenResolvers' +import { getFragmentDefinitions } from 'apollo-utilities' + +let reverseAddress = '0x3671aE578E63FdF66ad4F3E12CC0c0d71Ac7510C' +let reverseAbi = [ + { + inputs: [{ internalType: 'contract ENS', name: '_ens', type: 'address' }], + stateMutability: 'nonpayable', + type: 'constructor' + }, + { + inputs: [ + { internalType: 'address[]', name: 'addresses', type: 'address[]' } + ], + name: 'getNames', + outputs: [{ internalType: 'string[]', name: 'r', type: 'string[]' }], + stateMutability: 'view', + type: 'function' + } +] const deployerAbi = Deployer.abi @@ -43,6 +63,9 @@ const resolvers = { __typename: 'PartyMeta' })) }, + async getDeployer() { + return await getDeployerAddress() + }, async events() { const deployerAddress = await getDeployerAddress() const events = await getEvents(deployerAddress, deployerAbi) @@ -52,6 +75,45 @@ const resolvers = { address: event.args.deployedAddress, __typename: event.event })) + }, + async poapEventName(_, { eventId }) { + let response = await fetch(`https://api.poap.xyz/events/id/${eventId}`) + return response.json() + }, + async poapBadges(_, { userAddress }) { + let response = await fetch( + `https://api.poap.xyz/actions/scan/${userAddress}` + ) + return response.json() + }, + async getEnsName(_, { userAddress }) { + const web3 = await getWeb3ForNetwork('1') + const contract = new web3.eth.Contract(reverseAbi, reverseAddress).methods + + const name = await contract.getNames([userAddress]).call() + return { + name + } + }, + async getEnsAddress(_, { name }) { + const web3 = await getWeb3ForNetwork('1') + const address = await web3.eth.ens.getAddress(name) + + return { + address + } + }, + async getNFTs(_, { userAddress }) { + let pageNumber = 0 + // let url = `https://api.covalenthq.com/v1/1/address/${userAddress}/balances_v2/?key=${process.env.C_KEY}&nft=true&page-number=${pageNumber}&page-size=100` + let url = `https://api.covalenthq.com/v1/1/address/${userAddress}/balances_v2/?key=ckey_125f8d62ef8b4410a92c2787d6c&nft=true&page-number=${pageNumber}&page-size=100` + let data = await fetch(url) + let { + data: { items: items } + } = await data.json() + return items.filter(i => { + return !!i.nft_data + }) } }, diff --git a/src/api/utils.js b/src/api/utils.js index a5be1f8e..d5ce9dad 100644 --- a/src/api/utils.js +++ b/src/api/utils.js @@ -1,6 +1,7 @@ import { events } from '@wearekickback/contracts' import { parseLog } from 'ethereum-event-logs' - +import { getProvider } from '../GlobalState' +import _ from 'lodash' export const extractNewPartyAddressFromTx = tx => { // coerce events into logs if available if (tx.events) { @@ -15,9 +16,14 @@ export const extractNewPartyAddressFromTx = tx => { } export function txHelper(web3TxObj) { - return new Promise(resolve => { - web3TxObj.on('transactionHash', hash => { - resolve(hash) + getProvider().then(provider => { + return new Promise(resolve => { + web3TxObj.on('transactionHash', hash => { + if (provider.state.notify) { + provider.state.notify.hash(hash) + } + resolve(hash) + }) }) }) } @@ -43,3 +49,21 @@ export function lazyAsync(getter) { return promise } } + +export const parseAvatar = url => { + const ipfs = url.match(/^ipfs:\/\/(.*)/) + if (ipfs) { + const cid = ipfs[1] + return `https://ipfs.io/ipfs/${cid}` + } else { + return url + } +} + +export const participantsLength = participants => { + if (participants.length > 0) { + return _.maxBy(participants, a => a.index).index + } else { + return 0 + } +} diff --git a/src/api/web3.js b/src/api/web3.js index 23a62220..1fb164d1 100644 --- a/src/api/web3.js +++ b/src/api/web3.js @@ -119,6 +119,11 @@ export const pollForBlocks = web3 => { }, 10000) } +export const getWeb3ForNetwork = async id => { + let url = getNetworkProviderUrl(id) + return new Web3(new Web3.providers.HttpProvider(url)) +} + const getWeb3 = async () => { let web3 diff --git a/src/components/EventList/EventCard.js b/src/components/EventList/EventCard.js index c45e1a79..09dd4473 100644 --- a/src/components/EventList/EventCard.js +++ b/src/components/EventList/EventCard.js @@ -12,7 +12,7 @@ const Link = styled(DefaultLink)` ` const EventCardContainer = styled('li')` - background: #f8f9fb; + background: ${props => (props.private ? '#e9e9e9' : '#f8f9fb')}; box-shadow: 0px 10px 20px 0px rgba(0, 0, 0, 0.12); border-radius: 4px; overflow: hidden; @@ -49,14 +49,20 @@ const Deposit = styled('span')` font-style: italic; ` +const Private = styled(`span`)` + margin-left: 5px; + font-weight: bold; +` + class EventCard extends Component { render() { const { party } = this.props - const { address, headerImg, deposit, name, tokenAddress } = party - + const { address, headerImg, deposit, name, tokenAddress, status } = party return ( - + + {party.status === 'private' && Private} + {userAddress && ( - - {userAddress.slice(0, 6)}...{userAddress.slice(-4)} - + )} {loggedIn && userProfile && ( diff --git a/src/components/Links/AddressLink.js b/src/components/Links/AddressLink.js new file mode 100644 index 00000000..c2bd0664 --- /dev/null +++ b/src/components/Links/AddressLink.js @@ -0,0 +1,27 @@ +import React from 'react' +import styled from '@emotion/styled' +import { ENS_NAME_QUERY } from '../../graphql/queries' +import { useQuery } from 'react-apollo' +import EtherScanLink from '../Links/EtherScanLink' + +const AddressLink = ({ userAddress, prefix = '' }) => { + const { data: ensData } = useQuery(ENS_NAME_QUERY, { + variables: { userAddress } + }) + + const ensName = + ensData && + ensData.getEnsName && + ensData.getEnsName.name && + ensData.getEnsName.name[0] + + return ( + + {ensName + ? `${prefix}${ensName}` + : `${prefix}${userAddress.slice(0, 6)}...${userAddress.slice(-4)}`} + + ) +} + +export default AddressLink diff --git a/src/components/Links/EtherScanLink.js b/src/components/Links/EtherScanLink.js index 5385d9cb..27db117e 100644 --- a/src/components/Links/EtherScanLink.js +++ b/src/components/Links/EtherScanLink.js @@ -23,15 +23,22 @@ const EtherScanLink = ({ address, tx, children }) => ( return tx } } - + let host const prefix = '1' === expectedNetworkId ? '' : `${expectedNetworkName.toLowerCase()}.` + if (expectedNetworkId === '100') { + host = `https://blockscout.com/poa/xdai` + } else if (expectedNetworkId === '137') { + host = `https://polygonscan.com` + } else { + host = `https://${prefix}etherscan.io` + } let link if (address) { - link = `https://${prefix}etherscan.io/address/${address}` + link = `${host}/address/${address}` } else if (tx) { - link = `https://${prefix}etherscan.io/tx/${tx}` + link = `${host}/tx/${tx}` } return link ? ( diff --git a/src/components/Modal/Modal.js b/src/components/Modal/Modal.js index 46f54f65..e26ed683 100644 --- a/src/components/Modal/Modal.js +++ b/src/components/Modal/Modal.js @@ -55,7 +55,7 @@ const ModalContainer = styled('div')` display: flex; justify-content: center; align-items: center; - z-index: 2; + z-index: 3; ` const ModalContent = styled('div')` diff --git a/src/components/SingleEvent/ActivityList.js b/src/components/SingleEvent/ActivityList.js index 75ff9451..c0cc9967 100644 --- a/src/components/SingleEvent/ActivityList.js +++ b/src/components/SingleEvent/ActivityList.js @@ -1,7 +1,6 @@ import React, { Component } from 'react' import styled from '@emotion/styled' import moment from 'moment' -import Chat from './Chat' import { ReactTinyLink } from 'react-tiny-link' import _ from 'lodash' import { @@ -275,13 +274,6 @@ class SingleEventWrapper extends Component { })} - { - checkProgress() - }} - /> ) : ( No one is attending diff --git a/src/components/SingleEvent/Admin/ChangeDeposit.js b/src/components/SingleEvent/Admin/ChangeDeposit.js index 87f4025b..d7f10830 100644 --- a/src/components/SingleEvent/Admin/ChangeDeposit.js +++ b/src/components/SingleEvent/Admin/ChangeDeposit.js @@ -20,7 +20,7 @@ const TextInput = styled(DefaultTextInput)` const ChangeDeposit = ({ address, currentDeposit, numParticipants }) => { const depositToDisplay = toEthVal(currentDeposit) .toEth() - .toFixed(2) + .toFixed(3) const [text, setText] = useState('') return ( diff --git a/src/components/SingleEvent/Admin/CheckIn.js b/src/components/SingleEvent/Admin/CheckIn.js index c539a555..c4eafc33 100644 --- a/src/components/SingleEvent/Admin/CheckIn.js +++ b/src/components/SingleEvent/Admin/CheckIn.js @@ -1,22 +1,28 @@ -import React, { useState } from 'react' +import React, { useState, useEffect, useRef } from 'react' +import { useQuery } from 'react-apollo' import styled from '@emotion/styled' import useInterval from 'use-interval' +import { throttle } from 'lodash' import Button from '../../Forms/Button' import TextInput from '../../Forms/TextInput' import Label from '../../Forms/Label' import { withApollo } from 'react-apollo' -import { POAP_USERS_SUBGRAPH_QUERY } from '../../../graphql/queries' +import { + POAP_USERS_SUBGRAPH_QUERY, + POAP_EVENT_NAME_QUERY +} from '../../../graphql/queries' import { PARTICIPANT_STATUS } from '@wearekickback/shared' import { MARK_USER_ATTENDED } from '../../../graphql/mutations' import { ApolloClient } from 'apollo-client' import { InMemoryCache } from 'apollo-cache-inmemory' import { HttpLink } from 'apollo-link-http' +import { Link } from 'react-router-dom' const cache = new InMemoryCache() const link = new HttpLink({ - uri: 'https://api.thegraph.com/subgraphs/name/amxx/poap' + uri: 'https://api.thegraph.com/subgraphs/name/poap-xyz/poap-xdai' }) const POAP_ADDRESS = '0x22c1f6050e56d2876009903609a2cc3fef83b415' const graphClient = new ApolloClient({ cache, link }) @@ -41,9 +47,23 @@ const POAPList = styled('ul')` export default withApollo(function CheckIn({ party, client }) { const [poapId, setPoapId] = useState('') + const [eventId, setEventId] = useState('') const [newAttendees, setNewAttendees] = useState([]) const [isRunning, setIsRunning] = useState(false) + const { data: poapEventName } = useQuery(POAP_EVENT_NAME_QUERY, { + variables: { eventId }, + skip: !eventId + }) + const multiplePoap = + eventId && eventId.split(',').map(p => p.trim()).length > 0 + useEffect(() => { + if (party && party.optional && party.optional.poapId) { + setEventId(party.optional.poapId) + setPoapId(party.optional.poapId) + } + }, []) + useInterval( () => { const [attendee, ...rest] = newAttendees @@ -72,11 +92,11 @@ export default withApollo(function CheckIn({ party, client }) { query: POAP_USERS_SUBGRAPH_QUERY, variables: { eventId: poapId } }) - const addresses = event.tokens.map(t => t.owner.id) - + const addresses = (event && event.tokens.map(t => t.owner.id)) || [] + const tokens = (event && event.tokens) || [] const _newAttendees = party.participants .map(participant => { - let token = event.tokens.filter( + let token = tokens.filter( t => t.owner.id === participant.user.address )[0] if (token) { @@ -89,25 +109,14 @@ export default withApollo(function CheckIn({ party, client }) { addresses.includes(participant.user.address) && participant.status === PARTICIPANT_STATUS.REGISTERED ) + setIsRunning(false) setNewAttendees(_newAttendees) + setEventId(poapId) } - return ( <>
-

- If you are distributing NFTs using{' '} - POAP, then you can check in your - attendees automatically. -

- -

- Just enter your POAP event ID here so that it matches what is shown{' '} - here and Kickback - will mark users who received a POAP badge as attendees. -

-

You should only use this after users have had enough time to claim their NFTs! @@ -115,44 +124,66 @@ export default withApollo(function CheckIn({ party, client }) {

-

Enter your event ID and click below to check in your attendees.

- - - setPoapId(value)} - placeholder="POAP Event ID" - type="number" - /> - - - -
-
- {isRunning ? Auto checking in.... : ''} - {newAttendees.length} POAP tokens to claim. - - {newAttendees.map(a => { - const url = `https://opensea.io/assets/${POAP_ADDRESS}/${a.poapTokenId}` - return ( -
  • - - {a.poapTokenId} - {' '} - {a.user.username} {a.user.address.slice(0, 4)}... -
  • - ) - })} -
    + {poapEventName ? ( +
    + + + {eventId}: + {poapEventName.poapEventName && poapEventName.poapEventName.name} + + {multiplePoap ? ( +
    + One click check in is disabled for multiple POAP badges. Please + check in manually from participants page +
    + ) : ( + <> + + +

    + {isRunning ? Auto checking in.... : ''} + {newAttendees.length} POAP tokens to claim. + + {newAttendees.map(a => { + const url = `https://opensea.io/assets/${POAP_ADDRESS}/${a.poapTokenId}` + return ( +

  • + + {a.poapTokenId} + {' '} + {a.user.username} {a.user.address.slice(0, 4)}... +
  • + ) + })} + +

    + + )} +
    + ) : ( +

    + POAP ID is not set. Please set it at " + Event Detail" + tab +

    + )}
    ) diff --git a/src/components/SingleEvent/Admin/ClearAndSend.js b/src/components/SingleEvent/Admin/ClearAndSend.js new file mode 100644 index 00000000..98e8ca3b --- /dev/null +++ b/src/components/SingleEvent/Admin/ClearAndSend.js @@ -0,0 +1,50 @@ +import React, { useState } from 'react' +import styled from '@emotion/styled' +import { Mutation } from 'react-apollo' +import gql from 'graphql-tag' + +import Button from '../../Forms/Button' +import DefaultTextInput from '../../Forms/TextInput' + +const TextInput = styled(DefaultTextInput)` + margin-bottom: 10px; +` + +const CLEAR_AND_SEND = gql` + mutation clearAndSend($address: String, $num: String) { + clearAndSend(address: $address, num: $num) @client + } +` + +const ClearAndSend = ({ address, num }) => { + const [text, setText] = useState('') + return ( + + + {clearAndSend => ( + <> + + + + + )} + + + ) +} + +const Container = styled('div')`` + +export default ClearAndSend diff --git a/src/components/SingleEvent/Admin/CurrencyPicker.js b/src/components/SingleEvent/Admin/CurrencyPicker.js index b70c9342..931efd2e 100644 --- a/src/components/SingleEvent/Admin/CurrencyPicker.js +++ b/src/components/SingleEvent/Admin/CurrencyPicker.js @@ -1,6 +1,6 @@ import React from 'react' -const CurrencyPicker = ({ onChange, currencyType }) => { +const CurrencyPicker = ({ onChange, currencyType, nativeCurrencyType }) => { const onChangeHandler = e => { currencyType = e.target.value onChange(currencyType) @@ -13,25 +13,29 @@ const CurrencyPicker = ({ onChange, currencyType }) => { return ( <> - + {nativeCurrencyType === 'ETH' ? ( + + ) : ( + '' + )}