diff --git a/packages/page-parachains/src/Crowdloan/Fund.tsx b/packages/page-parachains/src/Crowdloan/Fund.tsx index 3545cd853e..b9d6c8b98f 100644 --- a/packages/page-parachains/src/Crowdloan/Fund.tsx +++ b/packages/page-parachains/src/Crowdloan/Fund.tsx @@ -3,6 +3,7 @@ import type BN from 'bn.js'; import type { Campaign } from '../types'; +import type { Contributed } from './types'; import React, { useMemo } from 'react'; @@ -17,11 +18,12 @@ import FundContribute from './FundContribute'; interface Props { bestNumber?: BN; className?: string; + contributed?: Contributed; isOngoing?: boolean; value: Campaign; } -function Fund ({ bestNumber, className, isOngoing, value: { info: { cap, depositor, end, firstSlot, lastSlot, raised, retiring }, isCapped, isEnded, isRetired, isWinner, paraId, retireEnd } }: Props): React.ReactElement { +function Fund ({ bestNumber, className, contributed, isOngoing, value: { info: { cap, depositor, end, firstSlot, lastSlot, raised, retiring }, isCapped, isEnded, isRetired, isWinner, paraId, retireEnd } }: Props): React.ReactElement { const { t } = useTranslation(); const { api } = useApi(); const { allAccounts } = useAccounts(); @@ -106,6 +108,11 @@ function Fund ({ bestNumber, className, isOngoing, value: { info: { cap, deposit />
{percentage}
+ + {contributed && ( + formatNumber(contributed.count) + )} + {canDissolve && ( { const { t } = useTranslation(); + const { api } = useApi(); + const trigger = useEventTrigger([api.events.crowdloan.Contributed]); + const [contributors, setContributors] = useState([]); const headerActiveRef = useRef([ [t('ongoing'), 'start', 4], [t('ending')], [t('periods')], [t('raised')], + [t('count')], [] ]); @@ -46,9 +52,29 @@ function Funds ({ bestNumber, className, leasePeriod, value }: Props): React.Rea [t('ending')], [t('periods')], [t('raised')], + [t('count')], [] ]); + useEffect((): void => { + trigger && value && + Promise + .all(value.map(({ childKey }) => api.rpc.childstate.getKeys(childKey, '0x'))) + .then((all) => setContributors( + all.map((keys, index) => ({ + accountIds: [], // keys.map((a) => encodeAddress(a)), + count: keys.length, + trieIndex: value[index].info.trieIndex + })) + )) + .catch(console.error); + }, [api, trigger, value]); + + const findContributions = useCallback( + (trieIndex: BN) => contributors.find((c) => trieIndex.eq(c.trieIndex)), + [contributors] + ); + const [active, ended] = useMemo( () => extractLists(value, leasePeriod), [leasePeriod, value] @@ -64,6 +90,7 @@ function Funds ({ bestNumber, className, leasePeriod, value }: Props): React.Rea {active?.map((fund) => ( ( diff --git a/packages/page-parachains/src/Crowdloan/types.ts b/packages/page-parachains/src/Crowdloan/types.ts new file mode 100644 index 0000000000..363e1f449c --- /dev/null +++ b/packages/page-parachains/src/Crowdloan/types.ts @@ -0,0 +1,10 @@ +// Copyright 2017-2021 @polkadot/app-parachains authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { TrieIndex } from '@polkadot/types/interfaces'; + +export interface Contributed { + accountIds: string[]; + count: number; + trieIndex: TrieIndex; +} diff --git a/packages/page-parachains/src/types.ts b/packages/page-parachains/src/types.ts index f752d844bc..5ab6b3f9dd 100644 --- a/packages/page-parachains/src/types.ts +++ b/packages/page-parachains/src/types.ts @@ -31,6 +31,7 @@ export interface Campaigns { } export interface Campaign extends WinnerData { + childKey: string; info: FundInfo; isCapped?: boolean; isEnded?: boolean; diff --git a/packages/page-parachains/src/useFunds.ts b/packages/page-parachains/src/useFunds.ts index ba59b8aeb9..a89f78a10f 100644 --- a/packages/page-parachains/src/useFunds.ts +++ b/packages/page-parachains/src/useFunds.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import type { Option } from '@polkadot/types'; -import type { AccountId, BalanceOf, BlockNumber, FundInfo, ParaId } from '@polkadot/types/interfaces'; +import type { AccountId, BalanceOf, BlockNumber, FundInfo, ParaId, TrieIndex } from '@polkadot/types/interfaces'; import type { ITuple } from '@polkadot/types/types'; import type { Campaign, Campaigns } from './types'; @@ -10,8 +10,8 @@ import BN from 'bn.js'; import { useEffect, useState } from 'react'; import { useApi, useBestNumber, useCall, useEventTrigger } from '@polkadot/react-hooks'; -import { BN_ZERO, u8aConcat } from '@polkadot/util'; -import { encodeAddress } from '@polkadot/util-crypto'; +import { BN_ZERO, u8aConcat, u8aToHex } from '@polkadot/util'; +import { blake2AsU8a, encodeAddress } from '@polkadot/util-crypto'; import { CROWD_PREFIX } from './constants'; @@ -37,6 +37,17 @@ function hasLease (paraId: ParaId, leased: ParaId[]): boolean { return leased.some((l) => l.eq(paraId)); } +function createChildKey (trieIndex: TrieIndex): string { + return u8aToHex( + u8aConcat( + ':child_storage:default:', + blake2AsU8a( + u8aConcat('crowdloan', trieIndex.toU8a()) + ) + ) + ); +} + // map into a campaign function updateFund (bestNumber: BN, minContribution: BN, retirementPeriod: BN, data: Campaign, leased: ParaId[]): Campaign { data.isCapped = data.info.cap.sub(data.info.raised).lt(minContribution); @@ -123,6 +134,7 @@ const optFundMulti = { .filter((v): v is [ParaId, FundInfo] => !!v[1]) .map(([paraId, info]): Campaign => ({ accountId: encodeAddress(createAddress(paraId)), + childKey: createChildKey(info.trieIndex), firstSlot: info.firstSlot, info, isCrowdloan: true,