diff --git a/src/assets/IconANT.svg b/src/assets/IconANT.svg new file mode 100755 index 00000000..8ba97700 --- /dev/null +++ b/src/assets/IconANT.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/IconDAI.svg b/src/assets/IconDAI.svg new file mode 100755 index 00000000..8a595495 --- /dev/null +++ b/src/assets/IconDAI.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/Dashboard/AccountBanner.js b/src/components/Dashboard/AccountBanner.js index 81e97941..756c5d23 100644 --- a/src/components/Dashboard/AccountBanner.js +++ b/src/components/Dashboard/AccountBanner.js @@ -4,11 +4,9 @@ import { GU, Help, LoadingRing, useTheme } from '@aragon/ui' import AccountBannerInfo from './AccountBannerInfo' import CircleGraph from '../CircleGraph' - import { useCourtConfig } from '../../providers/CourtConfig' -import { useTotalActiveBalancePolling } from '../../hooks/useCourtContracts' +import { useTotalActiveBalance } from '../../hooks/useCourtStats' import { useJurorFirstTimeANJActivation } from '../../hooks/useANJ' -import { useCourtClock } from '../../providers/CourtClock' import { ACCOUNT_STATUS_JUROR_ACTIVE } from '../../types/account-status-types' import { formatUnits, getPercentageBN, bigNum } from '../../lib/math-utils' @@ -164,10 +162,7 @@ const Wrapper = ({ mainIcon, information }) => { const BannerWithProbability = ({ activeBalance }) => { const theme = useTheme() - const { currentTermId } = useCourtClock() - const totalActiveBalanceCurrentTerm = useTotalActiveBalancePolling( - currentTermId - ) + const [totalActiveBalanceCurrentTerm] = useTotalActiveBalance() const fetchingTotalBalance = totalActiveBalanceCurrentTerm.eq(bigNum(-1)) if (fetchingTotalBalance) { diff --git a/src/components/Dashboard/Balance.js b/src/components/Dashboard/Balance.js index 56b377b0..ebafcdf0 100644 --- a/src/components/Dashboard/Balance.js +++ b/src/components/Dashboard/Balance.js @@ -4,6 +4,7 @@ import { animated, useSpring } from 'react-spring' import Loading from './Loading' import ANJLockedDistribution from './ANJLockedDistribution' +import SplitAmount from '../SplitAmount' import { useCourtConfig } from '../../providers/CourtConfig' import { useANJBalanceToUsd } from '../../hooks/useTokenBalanceToUsd' @@ -15,28 +16,6 @@ import { movementDirection, convertToString } from '../../types/anj-types' import ANJIcon from '../../assets/IconANJ.svg' import lockIcon from '../../assets/IconLock.svg' -const splitAmount = amount => { - const [integer, fractional] = amount.split('.') - return ( - - {integer} - {fractional && ( - - .{fractional} - - )} - - ) -} - const Balance = React.memo(function Balance({ label, amount, @@ -113,7 +92,9 @@ const Balance = React.memo(function Balance({ align-items: center; `} > - {splitAmount(formatUnits(amount, { digits: decimals }))} + ANJ { return ( - - + + {(() => { + if (fetching) { + return + } + return stats.map((stat, index) => { + return ( +
+ + {stat.label} + + {stat.token ? ( + + ) : ( + + {!stat.error ? stat.value : '-'} + + )} +
+ ) + }) + })()}
) } +function TokenStats({ stat, theme }) { + const { value, token, error } = stat + const { decimals, icon, symbol } = token + return ( + <> +
+ + {!error ? ( + + ) : ( + '-' + )} + + {!error && ( +
+ +
+ )} +
+ + $ + {!error + ? symbol === 'ANJ' + ? AnjUsdValue(value) + : TokenUsdValue(token, value) + : '-'} + + + ) +} + +function AnjUsdValue(amount) { + const usdValue = useANJBalanceToUsd(amount) + + return usdValue +} + +function TokenUsdValue(token, amount) { + const { decimals, symbol } = token + const usdValue = useTokenBalanceToUsd(symbol, decimals, amount) + return usdValue +} + export default CourtStats diff --git a/src/components/Dashboard/Dashboard.js b/src/components/Dashboard/Dashboard.js index 8991ab89..fc6bc406 100644 --- a/src/components/Dashboard/Dashboard.js +++ b/src/components/Dashboard/Dashboard.js @@ -11,6 +11,7 @@ import ActivateANJ from './panels/ActivateANJ' import WithdrawANJ from './panels/WithdrawANJ' import DeactivateANJ from './panels/DeactivateANJ' import AppealColateralModule from './AppealColateralModule' +import CourtStats from './CourtStats' import { useWallet } from '../../providers/Wallet' import { DashboardStateProvider } from './DashboardStateProvider' @@ -66,7 +67,11 @@ function Dashboard() { )} {!wallet.account ? ( - + } + secondary={} + invert="horizontal" + /> ) : ( } diff --git a/src/components/Dashboard/DashboardStats.js b/src/components/Dashboard/DashboardStats.js deleted file mode 100644 index bcb55206..00000000 --- a/src/components/Dashboard/DashboardStats.js +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react' -// import LatestActivity from './LatestActivity' -// import CourtStats from './CourtStats' - -const DashboardStats = () => { - return <>{/* */} -} - -export default DashboardStats diff --git a/src/components/SplitAmount.js b/src/components/SplitAmount.js new file mode 100644 index 00000000..32a7a47a --- /dev/null +++ b/src/components/SplitAmount.js @@ -0,0 +1,25 @@ +import React from 'react' +import { GU, textStyle } from '@aragon/ui' + +export default function SplitAmount({ amount }) { + const [integer, fractional] = amount.split('.') + return ( + + {integer} + {fractional && ( + + .{fractional} + + )} + + ) +} diff --git a/src/endpoints.js b/src/endpoints.js index 7009cd51..ec9f8ff9 100644 --- a/src/endpoints.js +++ b/src/endpoints.js @@ -26,7 +26,9 @@ function getAPIBase() { export default function endpoints() { const [API_BASE_HTTP, API_BASE_WS] = getAPIBase() - const networkType = getNetworkType(CHAIN_ID) + const networkType = isLocalOrUnknownNetwork() + ? 'rpc' + : getNetworkType(CHAIN_ID) const API_PATH = networkType === 'main' diff --git a/src/flagged-disputes/precedence-campaign-disputes.js b/src/flagged-disputes/precedence-campaign-disputes.js index 8eac529a..8def981f 100644 --- a/src/flagged-disputes/precedence-campaign-disputes.js +++ b/src/flagged-disputes/precedence-campaign-disputes.js @@ -1,25 +1,27 @@ import { networks, + getNetwork, + getInternalNetworkName, RINKEBY_COURT, RINKEBY_STAGING_COURT, RINKEBY_USABILITY_COURT, } from '../networks' -import { getNetworkType } from '../lib/web3-utils' const PRECEDENCE_CAMPAIGN_DISPUTES = { - rpc: new Map([[networks.rpc.court, ['0']]]), - ropsten: new Map([[networks.ropsten.court, []]]), + main: new Map([[networks.main.court, []]]), rinkeby: new Map([ [RINKEBY_COURT, []], [RINKEBY_STAGING_COURT, []], [RINKEBY_USABILITY_COURT, []], ]), - main: new Map([[networks.main.court, []]]), + ropsten: new Map([[networks.ropsten.court, []]]), + local: new Map([[networks.local.court, ['0']]]), } export function getPrecedenceCampaignDisputesByCourt() { - const networkType = getNetworkType() - const courtAddress = networks[networkType].court + const courtAddress = getNetwork().court - return PRECEDENCE_CAMPAIGN_DISPUTES[networkType].get(courtAddress) + return PRECEDENCE_CAMPAIGN_DISPUTES[getInternalNetworkName()].get( + courtAddress + ) } diff --git a/src/flagged-disputes/voided-disputes.js b/src/flagged-disputes/voided-disputes.js index 408e5f9d..b93fbce3 100644 --- a/src/flagged-disputes/voided-disputes.js +++ b/src/flagged-disputes/voided-disputes.js @@ -1,20 +1,14 @@ import { networks, + getInternalNetworkName, + getNetwork, RINKEBY_COURT, RINKEBY_STAGING_COURT, RINKEBY_USABILITY_COURT, } from '../networks' -import { getNetworkType } from '../lib/web3-utils' import env from '../environment' const VOIDED_DISPUTES = { - rpc: new Map([[networks.rpc.court, new Map([])]]), - ropsten: new Map([[networks.ropsten.court, new Map([])]]), - rinkeby: new Map([ - [RINKEBY_COURT, new Map([])], - [RINKEBY_USABILITY_COURT, new Map([])], - [RINKEBY_STAGING_COURT, new Map([])], - ]), main: new Map([ [ networks.main.court, @@ -32,15 +26,20 @@ const VOIDED_DISPUTES = { ), ], ]), + rinkeby: new Map([ + [RINKEBY_COURT, new Map([])], + [RINKEBY_USABILITY_COURT, new Map([])], + [RINKEBY_STAGING_COURT, new Map([])], + ]), + ropsten: new Map([[networks.ropsten.court, new Map([])]]), + local: new Map([[networks.local.court, new Map([])]]), } export function getVoidedDisputesByCourt() { if (env('SKIP_VOIDING')) { return new Map([]) } + const courtAddress = getNetwork().court - const networkType = getNetworkType() - const courtAddress = networks[networkType].court - - return VOIDED_DISPUTES[networkType].get(courtAddress) + return VOIDED_DISPUTES[getInternalNetworkName()].get(courtAddress) } diff --git a/src/hooks/query-hooks.js b/src/hooks/query-hooks.js index f619c49b..b80f2b24 100644 --- a/src/hooks/query-hooks.js +++ b/src/hooks/query-hooks.js @@ -2,7 +2,7 @@ import { useQuery } from 'urql' import { JurorDrafts } from '../queries/jurorDrafts' import { JurorFeesClaimed } from '../queries/juror' -import { FirstANJActivationMovement } from '../queries/balances' +import { ActiveJurors, FirstANJActivationMovement } from '../queries/balances' export function useJurorDraftQuery(jurorId) { const [result] = useQuery({ @@ -51,3 +51,11 @@ export function useFirstANJActivationQuery(jurorId, { pause = false }) { return juror ? juror.anjMovements[0] : null } + +export function useActiveJurorsNumber() { + const [{ data, error }] = useQuery({ + query: ActiveJurors, + }) + + return [data?.jurors?.length, error] +} diff --git a/src/hooks/subscription-hooks.js b/src/hooks/subscription-hooks.js index 6f260728..daf6c725 100644 --- a/src/hooks/subscription-hooks.js +++ b/src/hooks/subscription-hooks.js @@ -1,7 +1,11 @@ import { useMemo } from 'react' import { useSubscription } from 'urql' import { OpenTasks } from '../queries/tasks' -import { CourtConfig } from '../queries/court' +import { + CourtConfig, + FeeMovements, + JurorsRegistryModule, +} from '../queries/court' import { useCourtConfig } from '../providers/CourtConfig' import { SingleDispute, AllDisputes } from '../queries/disputes' import { AppealsByMaker, AppealsByTaker } from '../queries/appeals' @@ -10,14 +14,17 @@ import { JurorDraftsNotRewarded, CurrentTermJurorDrafts, } from '../queries/jurorDrafts' - +import { CourtModuleType } from '../types/court-module-types' import { bigNum } from '../lib/math-utils' import { dayjs } from '../utils/date-utils' import { groupMovements } from '../utils/anj-movement-utils' import { transformAppealDataAttributes } from '../utils/appeal-utils' import { transformDisputeDataAttributes } from '../utils/dispute-utils' import { transformJurorDataAttributes } from '../utils/juror-draft-utils' -import { transformCourtConfigDataAttributes } from '../utils/court-utils' +import { + getModuleAddress, + transformCourtConfigDataAttributes, +} from '../utils/court-utils' import { transformClaimedFeesDataAttributes } from '../utils/subscription-utils' const NO_AMOUNT = bigNum(0) @@ -320,3 +327,30 @@ export function useTasksSubscription() { return { tasks, fetching: !data && !error, error } } + +export function useJurorRegistrySubscription() { + const { modules } = useCourtConfig() + const jurorRegistryAddress = getModuleAddress( + modules, + CourtModuleType.JurorsRegistry + ) + + const [{ data, error }] = useSubscription({ + query: JurorsRegistryModule, + variables: { id: jurorRegistryAddress }, + }) + + const jurorRegistryStats = data?.jurorsRegistryModule || null + + return { data: jurorRegistryStats, error } +} + +export function useTotalRewardsSubscription() { + const [{ data, error }] = useSubscription({ + query: FeeMovements, + }) + + const rewards = data?.feeMovements || null + + return { data: rewards, error } +} diff --git a/src/hooks/useCourtContracts.js b/src/hooks/useCourtContracts.js index 68383cc1..92a8b48c 100644 --- a/src/hooks/useCourtContracts.js +++ b/src/hooks/useCourtContracts.js @@ -1,9 +1,12 @@ -import { useCallback, useEffect, useMemo, useState } from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { captureException } from '@sentry/browser' import { CourtModuleType } from '../types/court-module-types' -import { useContract } from '../web3-contracts' +import { useContract, useContractReadOnly } from '../web3-contracts' import { useCourtConfig } from '../providers/CourtConfig' -import { getFunctionSignature } from '../lib/web3-utils' +import { + getFunctionSignature, + isLocalOrUnknownNetwork, +} from '../lib/web3-utils' import { bigNum, formatUnits } from '../lib/math-utils' import { hashVote, @@ -14,6 +17,8 @@ import { import { getModuleAddress } from '../utils/court-utils' import { retryMax } from '../utils/retry-max' import { useActivity } from '../components/Activity/ActivityProvider' +import { networkAgentAddress, networkReserveAddress } from '../networks' +import { getKnownToken } from '../utils/known-tokens' import aragonCourtAbi from '../abi/AragonCourt.json' import courtSubscriptionsAbi from '../abi/CourtSubscriptions.json' @@ -564,44 +569,59 @@ export function useActiveBalanceOfAt(juror, termId) { return [activeBalance.amount, activeBalance.error] } -export function useTotalActiveBalancePolling(termId) { - const jurorRegistryContract = useCourtContract( - CourtModuleType.JurorsRegistry, - jurorRegistryAbi - ) - const [totalActiveBalance, setTotalActiveBalance] = useState(bigNum(-1)) +export function useTotalANTStakedPolling(timeout = 1000) { + const [totalANTStaked, setTotalANTStaked] = useState(bigNum(-1)) + const [error, setError] = useState(false) + const { address: antAddress } = getKnownToken('ANT') || {} + const antContract = useContractReadOnly(antAddress, tokenAbi) + + // We are starting in 0 in order to immediately make the fetch call + const controlledTimeout = useRef(0) useEffect(() => { let cancelled = false let timeoutId - const fetchTotalActiveBalance = () => { + // Since we don't have the ANT contract address on the local environment we are skipping the stat + if (isLocalOrUnknownNetwork()) { + setError(true) + return + } + if (!antContract) { + return + } + + const fetchTotalANTBalance = () => { timeoutId = setTimeout(() => { - return jurorRegistryContract - .totalActiveBalanceAt(termId) - .then(balance => { + const agentBalancePromise = antContract.balanceOf(networkAgentAddress) + const vaultBalancePromise = antContract.balanceOf(networkReserveAddress) + return Promise.all([agentBalancePromise, vaultBalancePromise]) + .then(([antInAgent, antInVault]) => { if (!cancelled) { - setTotalActiveBalance(balance) + setTotalANTStaked(antInAgent.add(antInVault)) } }) .catch(err => { console.error(`Error fetching balance: ${err} retrying...`) + setError(true) }) .finally(() => { if (!cancelled) { - fetchTotalActiveBalance() + clearTimeout(timeoutId) + controlledTimeout.current = timeout + fetchTotalANTBalance() } }) - }, 1000) + }, controlledTimeout.current) } - fetchTotalActiveBalance() + fetchTotalANTBalance() return () => { cancelled = true clearTimeout(timeoutId) } - }, [jurorRegistryContract, termId]) + }, [antContract, controlledTimeout, timeout]) - return totalActiveBalance + return [totalANTStaked, error] } diff --git a/src/hooks/useCourtStats.js b/src/hooks/useCourtStats.js new file mode 100644 index 00000000..309d2f83 --- /dev/null +++ b/src/hooks/useCourtStats.js @@ -0,0 +1,129 @@ +import { useMemo } from 'react' +import { useTotalANTStakedPolling } from './useCourtContracts' +import { useActiveJurorsNumber } from '../hooks/query-hooks' +import { + useJurorRegistrySubscription, + useTotalRewardsSubscription, +} from '../hooks/subscription-hooks' +import { getKnownToken } from '../utils/known-tokens' +import { bigNum } from '../lib/math-utils' +import IconANJ from '../assets/IconANJ.svg' +import IconANT from '../assets/IconANT.svg' +import IconDAI from '../assets/IconDAI.svg' + +const STATS_FETCHING_TIMEOUT = 15000 + +const COURT_STATS = [ + { + label: 'Total Active ANJ', + token: { ...getKnownToken('ANJ'), icon: IconANJ }, + }, + { + label: 'Total Staked ANT', + token: { ...getKnownToken('ANT'), icon: IconANT }, + }, + { label: 'Total Active Jurors' }, + { + label: 'Total Rewards DAI', + token: { ...getKnownToken('DAI'), icon: IconDAI }, + }, +] + +export function useTotalActiveBalance() { + const { data: jurorRegistryStats, error } = useJurorRegistrySubscription() + + return useMemo(() => { + if (!jurorRegistryStats || error) { + return [bigNum(-1), error] + } + return [bigNum(jurorRegistryStats.totalActive), error] + }, [error, jurorRegistryStats]) +} + +function useTotalRewards() { + const { data: rewards, error } = useTotalRewardsSubscription() + + return useMemo(() => { + if (!rewards || error) { + return [bigNum(-1), error] + } + return [ + rewards.reduce( + (totalAcumulator, reward) => totalAcumulator.add(reward.amount), + bigNum(0) + ), + error, + ] + }, [error, rewards]) +} +/** + * Hook to get the dashboard stats ANJ active balance, ANT total stake and the active jurors number + * @returns {Array} First item an array with the stats and the second one a loading state + */ +function useCourtStats() { + const [anjActiveBalance, anjActiveBalanceError] = useTotalActiveBalance() + const [antTotalStake, antTotalStakeError] = useTotalANTStakedPolling( + STATS_FETCHING_TIMEOUT + ) + const [activeJurors, activeJurorsError] = useActiveJurorsNumber() + const [totalRewards, totalRewardsError] = useTotalRewards() + + // Loading states + const anjFetching = anjActiveBalance.eq(bigNum(-1)) && !anjActiveBalanceError + const antFetching = antTotalStake.eq(bigNum(-1)) && !antTotalStakeError + const activeJurorsFetching = activeJurors === null && !activeJurorsError + const totalRewardsFetching = totalRewards.eq(bigNum(-1)) && !totalRewardsError + + return useMemo( + () => { + if ( + anjFetching || + antFetching || + activeJurorsFetching || + totalRewardsFetching + ) { + return [null, true] + } + + const statsData = [ + anjActiveBalance, + antTotalStake, + activeJurors, + totalRewards, + ] + const statsError = [ + anjActiveBalanceError, + antTotalStakeError, + activeJurorsError, + totalRewardsError, + ] + return [ + COURT_STATS.map((stat, index) => { + return { + ...stat, + value: statsData[index], + error: statsError[index], + } + }), + false, + ] + } /* eslint-disable react-hooks/exhaustive-deps */, + [ + activeJurors, + activeJurorsError, + activeJurorsFetching, + anjActiveBalance.toString(), + anjActiveBalanceError, + anjFetching, + antFetching, + antTotalStake.toString(), + antTotalStakeError, + totalRewards.toString(), + totalRewardsError, + totalRewardsFetching, + ] + /* eslint-disable-line react-hooks/exhaustive-deps */ + ) +} + +export default useCourtStats diff --git a/src/hooks/useTokenBalanceToUsd.js b/src/hooks/useTokenBalanceToUsd.js index 972129f3..7fb30416 100644 --- a/src/hooks/useTokenBalanceToUsd.js +++ b/src/hooks/useTokenBalanceToUsd.js @@ -4,7 +4,11 @@ import env from '../environment' import { useCourtConfig } from '../providers/CourtConfig' import { getKnownToken } from '../utils/known-tokens' import { formatUnits, bigNum } from '../lib/math-utils' -import { addressesEqual, ETH_FAKE_ADDRESS } from '../lib/web3-utils' +import { + addressesEqual, + ETH_FAKE_ADDRESS, + getNetworkType, +} from '../lib/web3-utils' const UNISWAP_PRECISION = 18 const UNISWAP_MARKET_RETRY_EVERY = 1000 @@ -43,7 +47,7 @@ export function useANJBalanceToUsd(amount) { const dai = getKnownToken('DAI') const { address: daiAddress } = dai || {} - const [convertedAmount, setConvertedAmount] = useState('0') + const [convertedAmount, setConvertedAmount] = useState('-') useEffect(() => { let cancelled = false @@ -54,31 +58,35 @@ export function useANJBalanceToUsd(amount) { } const updateConvertedAmount = async () => { - try { - const { marketRate } = await getANJMarketDetails( - daiAddress, - anjToken.id, - amount - ) - - const precision = bigNum(10).pow(UNISWAP_PRECISION) - - const rate = bigNum(marketRate.rateInverted.times(precision).toFixed(0)) + if (getNetworkType() === 'main') { + try { + const { marketRate } = await getANJMarketDetails( + daiAddress, + anjToken.id, + amount + ) - const convertedAmount = formatUnits(amount.mul(rate).div(precision), { - digits: anjToken.decimals, - }) + const precision = bigNum(10).pow(UNISWAP_PRECISION) - if (!cancelled) { - setConvertedAmount(convertedAmount) - } - } catch (err) { - console.error('Could not fetch Uniswap price for ANJ', err) - if (!cancelled) { - retryTimer = setTimeout( - updateConvertedAmount, - UNISWAP_MARKET_RETRY_EVERY + const rate = bigNum( + marketRate.rateInverted.times(precision).toFixed(0) ) + + const convertedAmount = formatUnits(amount.mul(rate).div(precision), { + digits: anjToken.decimals, + }) + + if (!cancelled) { + setConvertedAmount(convertedAmount) + } + } catch (err) { + console.error('Could not fetch Uniswap price for ANJ', err) + if (!cancelled) { + retryTimer = setTimeout( + updateConvertedAmount, + UNISWAP_MARKET_RETRY_EVERY + ) + } } } } @@ -102,11 +110,15 @@ export function useANJBalanceToUsd(amount) { * @param {BigNumber} balance The balance to convert into USD. * @returns { Number } The balance value in USD */ -export default function useTokenBalanceToUsd(symbol, decimals, balance) { +export function useTokenBalanceToUsd(symbol, decimals, balance) { const [usd, setUsd] = useState('-') useEffect(() => { let cancelled = false + if (getNetworkType() !== 'main') { + return + } + fetch(`${API_BASE}/price?fsym=${symbol}&tsyms=USD`) .then(res => res.json()) .then(price => { diff --git a/src/lib/web3-utils.js b/src/lib/web3-utils.js index 1093586b..01029b3a 100644 --- a/src/lib/web3-utils.js +++ b/src/lib/web3-utils.js @@ -3,7 +3,7 @@ import { solidityKeccak256, id as keccak256 } from 'ethers/utils' export const soliditySha3 = solidityKeccak256 export const hash256 = keccak256 -export const DEFAULT_LOCAL_CHAIN = 'rpc' +export const DEFAULT_LOCAL_CHAIN = 'private' export const ETH_FAKE_ADDRESS = `0x${''.padEnd(40, '0')}` const ETH_ADDRESS_SPLIT_REGEX = /(0x[a-fA-F0-9]{40}(?:\b|\.|,|\?|!|;))/g @@ -123,7 +123,7 @@ export function getNetworkName(chainId = env('CHAIN_ID')) { return 'unknown' } -export function isLocalOrUnknownNetwork(chainId) { +export function isLocalOrUnknownNetwork(chainId = env('CHAIN_ID')) { return getNetworkType(chainId) === DEFAULT_LOCAL_CHAIN } diff --git a/src/networks.js b/src/networks.js index eeb4b0f3..42deef80 100644 --- a/src/networks.js +++ b/src/networks.js @@ -1,4 +1,5 @@ import environment from './environment' +import { getNetworkType, isLocalOrUnknownNetwork } from './lib/web3-utils' const SUBGRAPH_NAME = environment('SUBGRAPH_NAME') @@ -10,15 +11,58 @@ export const RINKEBY_STAGING_COURT = '0x52180Af656A1923024D1ACcF1D827AB85cE48878' export const networks = { - rpc: { court: '0xD833215cBcc3f914bD1C9ece3EE7BF8B14f841bb' }, - ropsten: { court: '0x3b26bc496aebaed5b3E0E81cDE6B582CDe71396e' }, + main: { + court: '0xee4650cBe7a2B23701D416f58b41D8B76b617797', + network_agent: '0x5e8c17a6065c35b172b10e80493d2266e2947df4', + network_reserve: '0xec0dd1579551964703246becfbf199c27cb84485', + }, rinkeby: { - // Use the 'usability' Court address if declared + // Use the 'usability' or 'staging' Court address if declared court: getRinkebyCourtAddress(SUBGRAPH_NAME), }, - main: { court: '0xee4650cBe7a2B23701D416f58b41D8B76b617797' }, + ropsten: { court: '0x3b26bc496aebaed5b3E0E81cDE6B582CDe71396e' }, + local: { court: '0xD833215cBcc3f914bD1C9ece3EE7BF8B14f841bb' }, +} + +export const networkConfigs = { + main: { + nodes: { + defaultEth: 'https://mainnet.eth.aragon.network/', + }, + }, + rinkeby: { + nodes: { + defaultEth: 'https://rinkeby.eth.aragon.network/', + }, + }, + ropsten: { + nodes: { + defaultEth: 'https://ropsten.eth.aragon.network/', + }, + }, + local: { + nodes: { + defaultEth: 'http://localhost:8545', + }, + }, +} + +export function getInternalNetworkName() { + return isLocalOrUnknownNetwork() ? 'local' : getNetworkType() +} + +export function getNetwork() { + return networks[getInternalNetworkName()] } +export function getNetworkConfig() { + return networkConfigs[getInternalNetworkName()] +} + +export const networkAgentAddress = getNetwork().network_agent + +export const networkReserveAddress = getNetwork().network_reserve + function getRinkebyCourtAddress(subGraphName) { if (subGraphName === 'usability') { return RINKEBY_USABILITY_COURT diff --git a/src/providers/CourtConfig.js b/src/providers/CourtConfig.js index 434bec5d..1da2b14a 100644 --- a/src/providers/CourtConfig.js +++ b/src/providers/CourtConfig.js @@ -1,15 +1,12 @@ import React, { useContext } from 'react' import PropTypes from 'prop-types' -import environment from '../environment' import { useCourtConfigSubscription } from '../hooks/subscription-hooks' -import { getNetworkType } from '../lib/web3-utils' -import { networks } from '../networks' +import { getNetwork } from '../networks' -const CHAIN_ID = environment('CHAIN_ID') const CourtConfigContext = React.createContext() function CourtConfigProvider({ children }) { - const courtAddress = networks[getNetworkType(CHAIN_ID)].court + const courtAddress = getNetwork().court const courtConfig = useCourtConfigSubscription(courtAddress) return ( diff --git a/src/queries/balances.js b/src/queries/balances.js index ee8a3c1e..2338447e 100644 --- a/src/queries/balances.js +++ b/src/queries/balances.js @@ -64,3 +64,10 @@ export const FirstANJActivationMovement = gql` } } ` +export const ActiveJurors = gql` + query Jurors { + jurors(first: 1000, where: { activeBalance_gt: 0 }) { + id + } + } +` diff --git a/src/queries/court.js b/src/queries/court.js index a7cd6bac..0aeea393 100644 --- a/src/queries/court.js +++ b/src/queries/court.js @@ -60,3 +60,23 @@ export const CourtConfig = gql` } } ` +export const JurorsRegistryModule = gql` + subscription JurorsRegistryModule($id: ID!) { + jurorsRegistryModule(id: $id) { + id + totalStaked + totalActive + } + } +` + +export const FeeMovements = gql` + subscription FeeMovements { + feeMovements(where: { type_not: Withdraw }) { + id + type + amount + createdAt + } + } +` diff --git a/src/types/court-module-types.js b/src/types/court-module-types.js index 937214e4..66d25915 100644 --- a/src/types/court-module-types.js +++ b/src/types/court-module-types.js @@ -1,7 +1,7 @@ export const CourtModuleType = { AragonCourt: Symbol('COURT_MAIN_MODULE'), DisputeManager: Symbol('COURT_MODULE_DISPUTE_MANAGER'), - JurorsRegistry: Symbol('COURT_MDOULE_JURORS_REGISTRY'), + JurorsRegistry: Symbol('COURT_MODULE_JURORS_REGISTRY'), Voting: Symbol('COURT_MODULE_VOTING'), Treasury: Symbol('COURT_MODULE_TREASURY'), Subscriptions: Symbol('COURT_MODULE_SUBSCRIPTIONS'), diff --git a/src/utils/known-tokens.js b/src/utils/known-tokens.js index 550b93d1..9287d4e6 100644 --- a/src/utils/known-tokens.js +++ b/src/utils/known-tokens.js @@ -2,10 +2,65 @@ import env from '../environment' import { getNetworkType } from '../lib/web3-utils' export const KNOWN_TOKEN_BY_ENV = { + ANJ: { + main: { + address: '0xcD62b1C403fa761BAadFC74C525ce2B51780b184', + decimals: 18, + symbol: 'ANJ', + }, + rinkeby: { + address: '0x929F3B27a22a7A56FC8d89617033D22e53840aC9', + decimals: 18, + symbol: 'ANJ', + }, + ropsten: { + address: '0xc863E1CcC047befF17022F4229DBE6321A6BCe65', + decimals: 18, + symbol: 'ANJ', + }, + local: { + address: '0xcD62b1C403fa761BAadFC74C525ce2B51780b184', + decimals: 18, + symbol: 'ANJ', + }, + }, + ANT: { + main: { + address: '0x960b236A07cf122663c4303350609A66A7B288C0', + decimals: 18, + symbol: 'ANT', + }, + rinkeby: { + address: '0xbf932fdf8d600398d64614ef9a10401ff046f449', + decimals: 18, + symbol: 'ANT', + }, + ropsten: { + address: '0x0cb95D9537c8Fb0C947eD48FDafc66A7b72EfC86', + decimals: 18, + symbol: 'ANT', + }, + }, DAI: { main: { address: '0x6b175474e89094c44da98b954eedeac495271d0f', decimals: 18, + symbol: 'DAI', + }, + rinkeby: { + address: '0xe9A083D88Eed757B1d633321Ce0519F432c6284d', + decimals: 18, + symbol: 'DAI', + }, + ropsten: { + address: '0x4E1F48Db14D7E1ada090c42ffE15FF3024EEc8Bf', + decimals: 18, + symbol: 'DAI', + }, + local: { + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + decimals: 18, + symbol: 'DAI', }, }, } diff --git a/src/web3-contracts.js b/src/web3-contracts.js index b3baaae8..4eeae2b1 100644 --- a/src/web3-contracts.js +++ b/src/web3-contracts.js @@ -1,6 +1,7 @@ import { useMemo } from 'react' -import { Contract as EthersContract } from 'ethers' +import { Contract as EthersContract, providers as Providers } from 'ethers' import { useWallet } from './providers/Wallet' +import { getNetworkConfig } from './networks' export function useContract(address, abi, signer = true) { const { account, ethers } = useWallet() @@ -20,3 +21,19 @@ export function useContract(address, abi, signer = true) { ) }, [abi, account, address, ethers, signer]) } + +export function useContractReadOnly(address, abi) { + const ethEndpoint = getNetworkConfig().nodes.defaultEth + + const ethProvider = useMemo( + () => (ethEndpoint ? new Providers.JsonRpcProvider(ethEndpoint) : null), + [ethEndpoint] + ) + + return useMemo(() => { + if (!address) { + return null + } + return new EthersContract(address, abi, ethProvider) + }, [abi, address, ethProvider]) +}