From 2776ec02d98a702d47cf55cb112f685cd6778b2a Mon Sep 17 00:00:00 2001 From: Michael Bauer Date: Fri, 21 Feb 2020 09:38:47 +0100 Subject: [PATCH 1/5] ci: add travis config --- .travis.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..7c04be7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,17 @@ +os: linux +language: node_js +node_js: 12 +jobs: + include: + - name: "Semantic Release" + script: + - npm run build + - npx semantic-release + deploy: + provider: pages + skip_cleanup: true + token: $GITHUB_TOKEN + keep_history: true + local_dir: build + on: + branch: master From c4f2a209565efe1e69d954c1d6f2d81c8a76f56e Mon Sep 17 00:00:00 2001 From: Michael Bauer Date: Fri, 21 Feb 2020 10:07:54 +0100 Subject: [PATCH 2/5] ci: use node.js 10 for better compatibility --- .travis.yml | 2 +- package-lock.json | 52 ++++++++++++++++++++++++----------------------- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7c04be7..ceeff84 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ os: linux language: node_js -node_js: 12 +node_js: 10 jobs: include: - name: "Semantic Release" diff --git a/package-lock.json b/package-lock.json index 86d1421..c398aa6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5479,6 +5479,16 @@ } } }, + "style-loader": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.1.3.tgz", + "integrity": "sha512-rlkH7X/22yuwFYK357fMN/BxYOorfnfq0eD7+vqlemSK4wEcejFF1dg4zxP0euBW8NrYx2WZzZ8PPFevr7D+Kw==", + "dev": true, + "requires": { + "loader-utils": "^1.2.3", + "schema-utils": "^2.6.4" + } + }, "supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", @@ -25903,29 +25913,10 @@ "path-parse": "^1.0.6" } }, - "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - } - }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - }, - "style-loader": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.23.1.tgz", - "integrity": "sha512-XK+uv9kWwhZMZ1y7mysB+zoihsEj4wneFWAS5qoiLwzW0WzSqMrrsIy+a3zkQJq0ipFtBpX5W3MqyRIBF/WFGg==", - "requires": { - "loader-utils": "^1.1.0", - "schema-utils": "^1.0.0" - } } } }, @@ -28625,13 +28616,24 @@ } }, "style-loader": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.1.3.tgz", - "integrity": "sha512-rlkH7X/22yuwFYK357fMN/BxYOorfnfq0eD7+vqlemSK4wEcejFF1dg4zxP0euBW8NrYx2WZzZ8PPFevr7D+Kw==", - "dev": true, + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.23.1.tgz", + "integrity": "sha512-XK+uv9kWwhZMZ1y7mysB+zoihsEj4wneFWAS5qoiLwzW0WzSqMrrsIy+a3zkQJq0ipFtBpX5W3MqyRIBF/WFGg==", "requires": { - "loader-utils": "^1.2.3", - "schema-utils": "^2.6.4" + "loader-utils": "^1.1.0", + "schema-utils": "^1.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } } }, "stylehacks": { From 0035913dd2090d7d1357862691e1cf94fa9e9362 Mon Sep 17 00:00:00 2001 From: Michael Bauer Date: Fri, 21 Feb 2020 10:08:54 +0100 Subject: [PATCH 3/5] chore: set default version to 0.0.0development --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 84b7561..7b70c0d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "TAC", - "version": "0.0.0", + "version": "0.0.0development", "private": true, "homepage": "https://tac.dappstar.io", "repository": "github:TripleSpeeder/token-allowance-checker", From 2cf5b2ac84c8bfd7a207919a4a39be15da8cff9c Mon Sep 17 00:00:00 2001 From: Michael Bauer Date: Sat, 22 Feb 2020 20:33:52 +0100 Subject: [PATCH 4/5] feat: filter allowances Add text input to filter by token (Name, symbol or contract address). Add toggle to include/exclude entries withe zero allowance. --- src/components/AddressDisplay.js | 10 +- src/components/AllowanceLister.js | 115 ++++++------------ src/components/AllowancesListContainer.js | 95 +++++++++++++++ src/components/AllowancesListFilter.js | 45 +++++++ src/components/OnboardContext.js | 36 +++--- src/components/TokenAllowanceItem.js | 80 +++++++----- src/components/TokenAllowanceListContainer.js | 30 +++-- 7 files changed, 277 insertions(+), 134 deletions(-) create mode 100644 src/components/AllowancesListContainer.js create mode 100644 src/components/AllowancesListFilter.js diff --git a/src/components/AddressDisplay.js b/src/components/AddressDisplay.js index 158e01d..7e593bd 100644 --- a/src/components/AddressDisplay.js +++ b/src/components/AddressDisplay.js @@ -1,11 +1,17 @@ import React from 'react' import PropTypes from 'prop-types' import {Icon, Popup} from 'semantic-ui-react' -import useClippy from 'use-clippy' + const AddressDisplay = (props) => { const {address, ensName} = props - const [, setClipboard ] = useClippy() + const setClipboard = () => { + navigator.clipboard.writeText(address).then(function() { + /* clipboard successfully set */ + }, function() { + console.log(`failed to set clipboard`) + }) + } if (ensName) { return <> diff --git a/src/components/AllowanceLister.js b/src/components/AllowanceLister.js index e989d81..f0acf23 100644 --- a/src/components/AllowanceLister.js +++ b/src/components/AllowanceLister.js @@ -2,9 +2,10 @@ import React, {useContext, useEffect, useState} from 'react' import {useParams} from 'react-router-dom' import {createDfuseClient} from '@dfuse/client' import {Web3Context} from './OnboardContext' -import TokenAllowanceListContainer from './TokenAllowanceListContainer' import 'semantic-ui-css/semantic.min.css' -import {Icon, Message, Segment} from 'semantic-ui-react' +import {Segment} from 'semantic-ui-react' +import AllowancesListContainer from './AllowancesListContainer' +import AllowancesListFilter from './AllowancesListFilter' const topicHashApprove = '0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925' const eventABI = [ @@ -70,6 +71,20 @@ const AllowanceLister = () => { addressFromParams ? addressFromParams.toLowerCase() : '', ) const [page, setPage] = useState(0) + const [showZeroAllowances, setShowZeroAllowances] = useState(true) + const [addressFilter, setAddressFilter] = useState('') + + const toggleShowZeroAllowances = () => { + setShowZeroAllowances(!showZeroAllowances) + } + + const clearAddressFilter = () => { + setAddressFilter('') + } + + const handleAddressFilterChange = (e, {name, value}) => { + setAddressFilter(value) + } useEffect(() => { setAddress(addressFromParams ? addressFromParams.toLowerCase() : '') @@ -136,19 +151,19 @@ const AllowanceLister = () => { // double-check owner - Is this necessary? if (decoded.owner.toLowerCase() === address) { // Add tokenContract if its new - const tokenContract = logEntry.address - if (Object.keys(tokenSpenders).includes(tokenContract)) { + const tokenContractAddress = logEntry.address + if (Object.keys(tokenSpenders).includes(tokenContractAddress)) { // console.log(`tokenContract ${tokenContract} already known`) } else { - console.log(`Adding tokenContract ${tokenContract}`) - tokenSpenders[tokenContract] = [] + console.log(`Adding tokenContract ${tokenContractAddress}`) + tokenSpenders[tokenContractAddress] = [] } // Add spender address if its new - if (tokenSpenders[tokenContract].includes(decoded.spender)) { + if (tokenSpenders[tokenContractAddress].includes(decoded.spender)) { // console.log(`Spender ${decoded.spender} for ${tokenContract} already known`) } else { - console.log(`Adding Spender ${decoded.spender} for ${tokenContract}`) - tokenSpenders[tokenContract].push(decoded.spender) + console.log(`Adding Spender ${decoded.spender} for ${tokenContractAddress}`) + tokenSpenders[tokenContractAddress].push(decoded.spender) } } else { console.log(`Skipping log event due to owner mismatch. Expected ${address}, got ${decoded.owner}`) @@ -176,79 +191,27 @@ const AllowanceLister = () => { } }, [web3Context.web3, address]) - if (address === '') { - return ( - - - - - Enter an address to start! - - - - ) - } - - if (loading) { - return ( - - - - - Please wait while loading events -

Checking address: {address}

-
Querying dfuse API for ERC20 Approvals, getting page {page+1}...
-
-
-
- ) - } - - if (error) { - return ( - - - - - Error - {error} - - - - ) - } - - if (Object.keys(tokenSpenders).length === 0) { - return ( - - - - - No Approvals - Address {address} has no Approvals. - - - - ) - } - - const tokens = [] - for (const [key, value] of Object.entries(tokenSpenders)) { - tokens.push( - , - ) - } return (

Allowances of {address}:

- {tokens} + +
) } diff --git a/src/components/AllowancesListContainer.js b/src/components/AllowancesListContainer.js new file mode 100644 index 0000000..bacc7ef --- /dev/null +++ b/src/components/AllowancesListContainer.js @@ -0,0 +1,95 @@ +import React from 'react' +import PropTypes from 'prop-types' +import TokenAllowanceListContainer from './TokenAllowanceListContainer' +import {Icon, Message, Segment} from 'semantic-ui-react' + + +const AllowancesListContainer = ({tokenSpenders, address, showZeroAllowances, addressFilter, loading, error, page}) => { + + // TODO: useMemo + const tokens = [] + for (const [contractAddress, spenders] of Object.entries(tokenSpenders)) { + tokens.push( + , + ) + } + + if (loading) { + return ( + + + + + Please wait while loading events +

Checking address: {address}

+
Querying dfuse API for ERC20 Approvals, getting page {page+1}...
+
+
+
+ ) + } + + if (error) { + return ( + + + + + Error + {error} + + + + ) + } + + if (address === '') { + return ( + + + + + Enter an address to start! + + + + ) + } + + if (tokens.length === 0) { + return ( + + + + + No Approvals + Address {address} has no Approvals. + + + + ) + } + + return (<> + {tokens} + ) +} + +AllowancesListContainer.propTypes = { + tokenSpenders: PropTypes.object.isRequired, + address: PropTypes.string.isRequired, + showZeroAllowances: PropTypes.bool.isRequired, + addressFilter: PropTypes.string.isRequired, + loading: PropTypes.bool.isRequired, + error: PropTypes.string.isRequired, + page: PropTypes.number.isRequired, +} + +export default AllowancesListContainer \ No newline at end of file diff --git a/src/components/AllowancesListFilter.js b/src/components/AllowancesListFilter.js new file mode 100644 index 0000000..a57d96c --- /dev/null +++ b/src/components/AllowancesListFilter.js @@ -0,0 +1,45 @@ +import React from 'react' +import PropTypes from 'prop-types' +import {Checkbox, Grid, Icon, Input} from 'semantic-ui-react' + +const AllowancesListFilter = ( { showZeroAllowances, + toggleShowZeroAllowances, + addressFilterValue, + handleAddressFilterChange, + clearAddressFilter } ) => { + return ( + + + + } + fluid + autofocus + /> + + + + + + + ) +} + +AllowancesListFilter.propTypes = { + showZeroAllowances: PropTypes.bool.isRequired, + toggleShowZeroAllowances: PropTypes.func.isRequired, + addressFilterValue: PropTypes.string.isRequired, + handleAddressFilterChange: PropTypes.func.isRequired, + clearAddressFilter: PropTypes.func.isRequired, +} + +export default AllowancesListFilter \ No newline at end of file diff --git a/src/components/OnboardContext.js b/src/components/OnboardContext.js index 5c20974..ee3846e 100644 --- a/src/components/OnboardContext.js +++ b/src/components/OnboardContext.js @@ -14,6 +14,24 @@ export const Web3Context = createContext({ loginFunction: null, }) +const wallets = [ + { walletName: 'metamask', preferred: true }, + { walletName: 'coinbase', preferred: true }, + { + walletName: 'walletConnect', + infuraKey: '7f230a5ca832426796454c28577d93f2', + preferred: true + }, + { walletName: 'trust', preferred: true }, + { walletName: 'dapper', preferred: true }, + { walletName: 'authereum', preferred: true }, + { walletName: 'opera' }, + { walletName: 'status' }, + { walletName: 'operaTouch' }, + { walletName: 'torus' }, + { walletName: 'status' } +] + const OnboardContext = (props) => { const [web3, setWeb3] = useState() const [address, setAddress] = useState() @@ -22,24 +40,6 @@ const OnboardContext = (props) => { const [loggedIn, setLoggedIn] = useState(false) const [selected, setSelected] = useState(false) - const wallets = [ - { walletName: 'metamask', preferred: true }, - { walletName: 'coinbase', preferred: true }, - { - walletName: 'walletConnect', - infuraKey: '7f230a5ca832426796454c28577d93f2', - preferred: true - }, - { walletName: 'trust', preferred: true }, - { walletName: 'dapper', preferred: true }, - { walletName: 'authereum', preferred: true }, - { walletName: 'opera' }, - { walletName: 'status' }, - { walletName: 'operaTouch' }, - { walletName: 'torus' }, - { walletName: 'status' } - ] - useEffect(() => { console.log(`Initializing OnBoard.js...`) const onboard = (Onboard({ diff --git a/src/components/TokenAllowanceItem.js b/src/components/TokenAllowanceItem.js index e21e901..da81a3b 100644 --- a/src/components/TokenAllowanceItem.js +++ b/src/components/TokenAllowanceItem.js @@ -11,7 +11,18 @@ import TransactionModal from './TransactionModal' const unlimitedAllowance = new BN(2).pow(new BN(256)).subn(1) -const TokenAllowanceItem = ({tokenName, tokenAddress, tokenDecimals, tokenSupply, tokenSymbol, tokenContractInstance, ownerBalance, owner, spenders, spenderENSNames, allowances}) => { +const TokenAllowanceItem = ({ tokenName, + tokenAddress, + tokenDecimals, + tokenSupply, + tokenSymbol, + tokenContractInstance, + ownerBalance, + owner, + spenders, + spenderENSNames, + allowances, + showZeroAllowances }) => { const web3Context = useContext(Web3Context) const [editSpender, setEditSpender] = useState('') const [showEditModal, setShowEditModal] = useState(false) @@ -44,8 +55,8 @@ const TokenAllowanceItem = ({tokenName, tokenAddress, tokenDecimals, tokenSupply let result try { result = await tokenContractInstance.approve(editSpender, newAllowance.toString(), { - from: web3Context.address - }) + from: web3Context.address, + }) setTransactionHash(result.tx) } catch (e) { console.log(`Error while approving: ${e.message}`) @@ -67,15 +78,14 @@ const TokenAllowanceItem = ({tokenName, tokenAddress, tokenDecimals, tokenSupply let allowanceElement let criticalAllowance = false let editEnabled = false + let value = undefined let loaded = BN.isBN(allowances[spender]) && BN.isBN(tokenDecimals) && BN.isBN(tokenSupply) if (loaded) { - const value = allowances[spender] + value = allowances[spender] // wallet account has to be owner in order to edit allowance editEnabled = (owner.toLowerCase() === web3Context.address.toLowerCase()) criticalAllowance = (value.eq(unlimitedAllowance)) || (value.gte(tokenSupply)) if (criticalAllowance) { - // \u221E is 'infinity' - // allowanceElement = {'\u221E'} allowanceElement = unlimited } else { const decimals = tokenDecimals @@ -86,29 +96,39 @@ const TokenAllowanceItem = ({tokenName, tokenAddress, tokenDecimals, tokenSupply } else { allowanceElement = } - rows.push( - - - - - - {allowanceElement} - - -