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

Commit 20e833f

Browse files
author
Narb
authored
Merge pull request #160 from embiem/add-nft-supply-hook
Add hook to get the DEV NFT supply
2 parents f645759 + 300431d commit 20e833f

File tree

11 files changed

+2032
-3
lines changed

11 files changed

+2032
-3
lines changed

frontend/.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@ npm-debug.log*
2222
yarn-debug.log*
2323
yarn-error.log*
2424

25-
.next
25+
.next
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { BigNumber } from '@ethersproject/bignumber';
2+
import { renderHook } from '@testing-library/react-hooks';
3+
import crypto from 'crypto';
4+
5+
import { useDevNFTSupply } from '../../src/hooks/useDevNFTSupply';
6+
import { Ddao__factory } from '../../types/ethers-contracts/factories/Ddao__factory';
7+
8+
jest.mock('../../types/ethers-contracts/factories/Ddao__factory');
9+
10+
const mockedDdaoFactory = Ddao__factory as jest.Mocked<typeof Ddao__factory>;
11+
12+
test('should return correct supply values & transition loading state', async () => {
13+
mockedDdaoFactory.connect.mockImplementation((address, provider) => {
14+
return {
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: () => {} },
30+
} as any;
31+
});
32+
33+
const { result, waitForValueToChange } = renderHook(() => useDevNFTSupply());
34+
35+
expect(result.current.isLoading).toBe(true);
36+
await waitForValueToChange(() => result.current.isLoading);
37+
expect(result.current.isLoading).toBe(false);
38+
39+
expect(result.current.maxSupply).toBe(8000);
40+
expect(result.current.totalSupply).toBe(2);
41+
expect(result.current.lockedSupply).toBe(1);
42+
expect(result.current.publicSupply).toBe(1);
43+
44+
expect(result.current.uniqueTokenHolders).toBe(1);
45+
46+
expect(result.current.remainingTotalSupply).toBe(8000 - 2);
47+
expect(result.current.remainingPublicSupply).toBe(7777 - 1);
48+
expect(result.current.remainingLockedSupply).toBe(8000 - 7777 - 1);
49+
});
50+
51+
test('should correctly handle "all minted" state', async () => {
52+
mockedDdaoFactory.connect.mockImplementation((address, provider) => {
53+
return {
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: () => {} },
63+
} as any;
64+
});
65+
66+
const { result, waitForValueToChange } = renderHook(() => useDevNFTSupply());
67+
68+
expect(result.current.isLoading).toBe(true);
69+
await waitForValueToChange(() => result.current.isLoading);
70+
expect(result.current.isLoading).toBe(false);
71+
72+
expect(result.current.maxSupply).toBe(8000);
73+
expect(result.current.totalSupply).toBe(8000);
74+
expect(result.current.lockedSupply).toBe(8000 - 7777);
75+
expect(result.current.publicSupply).toBe(7777);
76+
77+
expect(result.current.uniqueTokenHolders).toBe(8000);
78+
79+
expect(result.current.remainingTotalSupply).toBe(0);
80+
expect(result.current.remainingPublicSupply).toBe(0);
81+
expect(result.current.remainingLockedSupply).toBe(0);
82+
});

frontend/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@
3939
"contributors:generate": "all-contributors generate",
4040
"type-check": "tsc --noEmit",
4141
"test": "jest",
42-
"test:watch": "jest --watch --detectOpenHandles"
42+
"test:watch": "jest --watch --detectOpenHandles",
43+
"generate-types": "typechain --target ethers-v5 src/artifacts/*.json",
44+
"postinstall": "yarn generate-types"
4345
},
4446
"eslintConfig": {
4547
"extends": [
@@ -63,6 +65,7 @@
6365
"devDependencies": {
6466
"@testing-library/cypress": "^8.0.1",
6567
"@testing-library/react-hooks": "^7.0.2",
68+
"@typechain/ethers-v5": "^8.0.1",
6669
"all-contributors-cli": "^6.20.0",
6770
"cypress": "^8.4.1",
6871
"eslint": "^7.32.0",
@@ -73,6 +76,7 @@
7376
"husky": "^7.0.2",
7477
"lint-staged": "^11.1.2",
7578
"prettier": "2.3.2",
79+
"typechain": "^6.0.2",
7680
"typescript": "^4.4.3"
7781
},
7882
"lint-staged": {

frontend/public/locales/en/common.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +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 / {{uniqueAddressCount}} devs joined",
3334
"mintPageHeader": "DEVS Token Minter",
3435
"projectsList": "A list of community projects created by the Developer DAO community.",
3536
"ddaoTokenSearch": "DDAO Token Search",

frontend/src/components/DirectMint/DirectMintBox.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +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';
1516

1617
// Layout for the Direct Mint Box
1718
// used on the minting page
1819
const DirectMintBox = () => {
1920
const { t } = useTranslation();
21+
const { isLoading, remainingPublicSupply, uniqueTokenHolders } =
22+
useDevNFTSupply();
23+
2024
return (
2125
<>
2226
<Container maxW="container.md" centerContent>
@@ -37,6 +41,16 @@ const DirectMintBox = () => {
3741
{t('here')}
3842
</Link>
3943
</Text>
44+
<Text fontSize={{ base: 'xs', sm: 'xl' }}>
45+
{t('remainingTokensText', {
46+
remainingTokens: isLoading
47+
? '...'
48+
: remainingPublicSupply.toLocaleString(),
49+
uniqueAddressCount: isLoading
50+
? '...'
51+
: uniqueTokenHolders?.toLocaleString(),
52+
})}
53+
</Text>
4054
<DirectMint />
4155
</Stack>
4256
</Box>

frontend/src/hooks/useDevNFTSupply.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { useEffect, useState } from 'react';
2+
import { ethers } from 'ethers';
3+
import {
4+
AlchemyProvider,
5+
FallbackProvider,
6+
InfuraProvider,
7+
} from '@ethersproject/providers';
8+
9+
import { DEVELOPER_DAO_CONTRACT } from '../utils/DeveloperDaoConstants';
10+
import { Ddao__factory } from '../../types/ethers-contracts/factories/Ddao__factory';
11+
12+
// TokenIDs start at 1. Tokens 1-7777 (supply of 7777) are open to mint by anyone. 7778-8000 are locked to mint by contract owner
13+
const MAX_SUPPLY = 8000;
14+
const PUBLIC_MAX_SUPPLY = 7777;
15+
16+
export function useDevNFTSupply() {
17+
const [totalSupply, setTotalSupply] = useState<number>(-1);
18+
const [lockedSupply, setLockedSupply] = useState<number>(-1);
19+
const [uniqueTokenHolderCount, setUniqueTokenHolderCount] =
20+
useState<number>(-1);
21+
22+
useEffect(() => {
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;
48+
49+
// Put the addresses in a set to get the amount of unique addresses
50+
const uniqueAddresses = new Set<string>(Object.values(claimedTokens));
51+
52+
// Count how many tokens of the locked supply have been minted
53+
let countLockedAndMinted = 0;
54+
for (
55+
let lockedTokenId = PUBLIC_MAX_SUPPLY + 1;
56+
lockedTokenId <= MAX_SUPPLY;
57+
lockedTokenId++
58+
) {
59+
if (
60+
claimedTokens[lockedTokenId] &&
61+
claimedTokens[lockedTokenId] !== ethers.constants.AddressZero
62+
)
63+
countLockedAndMinted++;
64+
}
65+
66+
setLockedSupply(countLockedAndMinted);
67+
setTotalSupply(totalSupply);
68+
setUniqueTokenHolderCount(uniqueAddresses.size);
69+
};
70+
71+
fetch();
72+
}, []);
73+
74+
return {
75+
isLoading:
76+
totalSupply === -1 ||
77+
lockedSupply === -1 ||
78+
uniqueTokenHolderCount === -1,
79+
80+
// This is the amount of total minted DEV NFTs, same as calling `totalSupply()` on the contract
81+
totalSupply,
82+
83+
// Count of unique token holder addresses
84+
uniqueTokenHolders: uniqueTokenHolderCount,
85+
86+
// This is the amount of minted DEV NFTs that only the contract owner can mint (TokenIDs 7778-8000, incl.)
87+
lockedSupply,
88+
89+
// This is the amount of publicly minted DEV NFTs, which is open to mint by anyone (TokenIDs 1-7777, incl.)
90+
publicSupply: totalSupply - lockedSupply,
91+
92+
// Total number of possible DEV NFT. This is a constant that cannot be changed
93+
maxSupply: MAX_SUPPLY,
94+
95+
// Some calculations to make these values easily accessible for display in UI
96+
remainingTotalSupply: MAX_SUPPLY - totalSupply,
97+
remainingPublicSupply: PUBLIC_MAX_SUPPLY - (totalSupply - lockedSupply),
98+
remainingLockedSupply: MAX_SUPPLY - PUBLIC_MAX_SUPPLY - lockedSupply,
99+
};
100+
}

0 commit comments

Comments
 (0)