Skip to content
This repository was archived by the owner on Dec 26, 2023. It is now read-only.

Commit 300431d

Browse files
committed
Use queryFilter & get unique address count
1 parent c6c4a7f commit 300431d

File tree

4 files changed

+99
-58
lines changed

4 files changed

+99
-58
lines changed

frontend/_tests_/hooks/useDevNFTSupply.test.ts

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,32 @@
1+
import { BigNumber } from '@ethersproject/bignumber';
12
import { renderHook } from '@testing-library/react-hooks';
3+
import crypto from 'crypto';
24

3-
import useDevNFTSupply from '../../src/hooks/useDevNFTSupply';
5+
import { useDevNFTSupply } from '../../src/hooks/useDevNFTSupply';
46
import { Ddao__factory } from '../../types/ethers-contracts/factories/Ddao__factory';
57

68
jest.mock('../../types/ethers-contracts/factories/Ddao__factory');
9+
710
const mockedDdaoFactory = Ddao__factory as jest.Mocked<typeof Ddao__factory>;
811

912
test('should return correct supply values & transition loading state', async () => {
10-
mockedDdaoFactory.connect.mockImplementationOnce((address, provider) => {
13+
mockedDdaoFactory.connect.mockImplementation((address, provider) => {
1114
return {
12-
totalSupply: async () => ({ toNumber: () => 2345 }),
13-
ownerOf: async (tokenId: number) => {
14-
if (tokenId === 8000)
15-
return '0x91d7a9e7c09477392290fe16c1b243e4a36d279a';
16-
17-
throw Error();
18-
},
15+
queryFilter: async () => [
16+
{
17+
args: {
18+
tokenId: BigNumber.from(1),
19+
to: '0x91d7a9e7c09477392290fe16c1b243e4a36d279a',
20+
},
21+
},
22+
{
23+
args: {
24+
tokenId: BigNumber.from(8000),
25+
to: '0x91d7a9e7c09477392290fe16c1b243e4a36d279a',
26+
},
27+
},
28+
],
29+
filters: { Transfer: () => {} },
1930
} as any;
2031
});
2132

@@ -26,21 +37,29 @@ test('should return correct supply values & transition loading state', async ()
2637
expect(result.current.isLoading).toBe(false);
2738

2839
expect(result.current.maxSupply).toBe(8000);
29-
expect(result.current.totalSupply).toBe(2345);
40+
expect(result.current.totalSupply).toBe(2);
3041
expect(result.current.lockedSupply).toBe(1);
31-
expect(result.current.publicSupply).toBe(2344);
42+
expect(result.current.publicSupply).toBe(1);
3243

33-
expect(result.current.remainingTotalSupply).toBe(8000 - 2345);
34-
expect(result.current.remainingPublicSupply).toBe(7777 - 2344);
44+
expect(result.current.uniqueTokenHolders).toBe(1);
45+
46+
expect(result.current.remainingTotalSupply).toBe(8000 - 2);
47+
expect(result.current.remainingPublicSupply).toBe(7777 - 1);
3548
expect(result.current.remainingLockedSupply).toBe(8000 - 7777 - 1);
3649
});
3750

3851
test('should correctly handle "all minted" state', async () => {
39-
mockedDdaoFactory.connect.mockImplementationOnce((address, provider) => {
52+
mockedDdaoFactory.connect.mockImplementation((address, provider) => {
4053
return {
41-
totalSupply: async () => ({ toNumber: () => 8000 }),
42-
ownerOf: async (tokenId: number) =>
43-
'0x91d7a9e7c09477392290fe16c1b243e4a36d279a',
54+
totalSupply: async () => ({ toNumber: () => 1 }),
55+
queryFilter: async () =>
56+
[...Array(8000)].map((_, idx) => ({
57+
args: {
58+
tokenId: BigNumber.from(idx + 1),
59+
to: `0x${crypto.randomBytes(32).toString('hex')}`,
60+
},
61+
})),
62+
filters: { Transfer: () => {} },
4463
} as any;
4564
});
4665

@@ -55,6 +74,8 @@ test('should correctly handle "all minted" state', async () => {
5574
expect(result.current.lockedSupply).toBe(8000 - 7777);
5675
expect(result.current.publicSupply).toBe(7777);
5776

77+
expect(result.current.uniqueTokenHolders).toBe(8000);
78+
5879
expect(result.current.remainingTotalSupply).toBe(0);
5980
expect(result.current.remainingPublicSupply).toBe(0);
6081
expect(result.current.remainingLockedSupply).toBe(0);

frontend/public/locales/en/common.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"connectWalletText": "Connect Wallet",
3131
"ethereumNetworkPrompt": "Please Connect to the Ethereum Mainnet",
3232
"availableTokensText": "View the available Token IDs",
33-
"remainingTokensText": "{{remainingTokens}} tokens left / {{totalSupply}} already joined",
33+
"remainingTokensText": "{{remainingTokens}} tokens left / {{uniqueAddressCount}} devs joined",
3434
"mintPageHeader": "DEVS Token Minter",
3535
"projectsList": "A list of community projects created by the Developer DAO community.",
3636
"ddaoTokenSearch": "DDAO Token Search",

frontend/src/components/DirectMint/DirectMintBox.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ import {
1212
} from '@chakra-ui/react';
1313
import { useTranslation } from 'next-i18next';
1414
import { TOKEN_FINDER_URL } from '../../utils/DeveloperDaoConstants';
15-
import useDevNFTSupply from '../../hooks/useDevNFTSupply';
15+
import { useDevNFTSupply } from '../../hooks/useDevNFTSupply';
1616

1717
// Layout for the Direct Mint Box
1818
// used on the minting page
1919
const DirectMintBox = () => {
2020
const { t } = useTranslation();
21-
const { isLoading, remainingPublicSupply, totalSupply } = useDevNFTSupply();
21+
const { isLoading, remainingPublicSupply, uniqueTokenHolders } =
22+
useDevNFTSupply();
23+
2224
return (
2325
<>
2426
<Container maxW="container.md" centerContent>
@@ -44,7 +46,9 @@ const DirectMintBox = () => {
4446
remainingTokens: isLoading
4547
? '...'
4648
: remainingPublicSupply.toLocaleString(),
47-
totalSupply: isLoading ? '...' : totalSupply.toLocaleString(),
49+
uniqueAddressCount: isLoading
50+
? '...'
51+
: uniqueTokenHolders?.toLocaleString(),
4852
})}
4953
</Text>
5054
<DirectMint />

frontend/src/hooks/useDevNFTSupply.ts

Lines changed: 53 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -13,69 +13,85 @@ import { Ddao__factory } from '../../types/ethers-contracts/factories/Ddao__fact
1313
const MAX_SUPPLY = 8000;
1414
const PUBLIC_MAX_SUPPLY = 7777;
1515

16-
export default function useDevNFTSupply() {
16+
export function useDevNFTSupply() {
1717
const [totalSupply, setTotalSupply] = useState<number>(-1);
1818
const [lockedSupply, setLockedSupply] = useState<number>(-1);
19+
const [uniqueTokenHolderCount, setUniqueTokenHolderCount] =
20+
useState<number>(-1);
1921

2022
useEffect(() => {
21-
const contract = Ddao__factory.connect(
22-
DEVELOPER_DAO_CONTRACT,
23-
new FallbackProvider([
24-
{ provider: new InfuraProvider() },
25-
{ provider: new AlchemyProvider() },
26-
]),
27-
);
28-
29-
// Fetches the count of minted DEV NFTs
30-
const fetchTotalSupply = async () => {
31-
const totalSupply: number = (await contract.totalSupply()).toNumber();
32-
setTotalSupply(totalSupply);
33-
};
23+
const fetch = async () => {
24+
const contract = Ddao__factory.connect(
25+
DEVELOPER_DAO_CONTRACT,
26+
new FallbackProvider([
27+
{ provider: new InfuraProvider() },
28+
{ provider: new AlchemyProvider() },
29+
]),
30+
);
31+
32+
// Get all TransferEvents
33+
const transferredTokens = await contract.queryFilter(
34+
contract.filters.Transfer(),
35+
0,
36+
'latest',
37+
);
38+
39+
// Track the latest owner of each token
40+
const claimedTokens: Record<string, string> = {};
41+
transferredTokens.forEach((tx) => {
42+
const tokenId = tx.args.tokenId.toNumber();
43+
claimedTokens[tokenId] = tx.args.to;
44+
});
45+
46+
// Amount of minted tokens
47+
const totalSupply = Object.keys(claimedTokens).length;
3448

35-
// Counts how many tokens of the locked supply have been minted
36-
const fetchLockedSupply = async () => {
37-
const requests = [];
49+
// Put the addresses in a set to get the amount of unique addresses
50+
const uniqueAddresses = new Set<string>(Object.values(claimedTokens));
3851

52+
// Count how many tokens of the locked supply have been minted
53+
let countLockedAndMinted = 0;
3954
for (
4055
let lockedTokenId = PUBLIC_MAX_SUPPLY + 1;
4156
lockedTokenId <= MAX_SUPPLY;
4257
lockedTokenId++
4358
) {
44-
requests.push(
45-
contract.ownerOf(lockedTokenId).then(
46-
// Return the owner's address
47-
(addressResponse) => addressResponse,
48-
// Return the zero-address if no owner was found
49-
() => ethers.constants.AddressZero,
50-
),
51-
);
59+
if (
60+
claimedTokens[lockedTokenId] &&
61+
claimedTokens[lockedTokenId] !== ethers.constants.AddressZero
62+
)
63+
countLockedAndMinted++;
5264
}
5365

54-
const addresses = await Promise.all(requests);
55-
56-
const countMinted = addresses.reduce(
57-
(count, address) =>
58-
address !== ethers.constants.AddressZero ? ++count : count,
59-
0,
60-
);
61-
62-
setLockedSupply(countMinted);
66+
setLockedSupply(countLockedAndMinted);
67+
setTotalSupply(totalSupply);
68+
setUniqueTokenHolderCount(uniqueAddresses.size);
6369
};
6470

65-
fetchTotalSupply();
66-
fetchLockedSupply();
71+
fetch();
6772
}, []);
6873

6974
return {
70-
isLoading: totalSupply === -1 || lockedSupply === -1,
75+
isLoading:
76+
totalSupply === -1 ||
77+
lockedSupply === -1 ||
78+
uniqueTokenHolderCount === -1,
79+
7180
// This is the amount of total minted DEV NFTs, same as calling `totalSupply()` on the contract
7281
totalSupply,
82+
83+
// Count of unique token holder addresses
84+
uniqueTokenHolders: uniqueTokenHolderCount,
85+
7386
// This is the amount of minted DEV NFTs that only the contract owner can mint (TokenIDs 7778-8000, incl.)
7487
lockedSupply,
88+
7589
// This is the amount of publicly minted DEV NFTs, which is open to mint by anyone (TokenIDs 1-7777, incl.)
7690
publicSupply: totalSupply - lockedSupply,
91+
7792
// Total number of possible DEV NFT. This is a constant that cannot be changed
7893
maxSupply: MAX_SUPPLY,
94+
7995
// Some calculations to make these values easily accessible for display in UI
8096
remainingTotalSupply: MAX_SUPPLY - totalSupply,
8197
remainingPublicSupply: PUBLIC_MAX_SUPPLY - (totalSupply - lockedSupply),

0 commit comments

Comments
 (0)