-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathuseMembers.ts
162 lines (145 loc) · 4.73 KB
/
useMembers.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
/**
* This program has been developed by students from the bachelor Computer Science at Utrecht University within the Software Project course.
* © Copyright Utrecht University (Department of Information and Computing Sciences)
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import { useAragonSDKContext } from '@/src/context/AragonSDK';
import { CHAIN_METADATA } from '@/src/lib/constants/chains';
import { getErrorMessage } from '@/src/lib/utils';
import { TokenVotingClient } from '@aragon/sdk-client';
import { BigNumber } from 'ethers';
import { useEffect, useState } from 'react';
type UseMembersProps = {
useDummyData?: boolean;
includeBalances?: boolean;
limit?: number | undefined;
};
type UseMembersData = {
loading: boolean;
error: string | null;
members: Member[];
memberCount: number;
isMember: (address: string) => boolean;
};
export type Member = { address: string; bal: number | null };
const dummyMembers: Member[] = [];
export const useMembers = ({
useDummyData = false,
includeBalances = true,
limit = undefined,
}: UseMembersProps): UseMembersData => {
const [members, setMembers] = useState<Member[]>([]);
const [memberCount, setMemberCount] = useState<number>(0);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
const { votingClient, votingPluginAddress, repTokenContract } =
useAragonSDKContext();
/**
* Fetch the REP balance for a single address. Throws an error if the REP token contract is not set
* @returns The REP balance of the given address
*/
const fetchBalance = async (address: string): Promise<BigNumber> => {
if (!repTokenContract) throw new Error('REP token contract not set');
return repTokenContract.balanceOf(address);
};
/**
* Fetch balances for a list of addresses
* @param addressList List of addresses to fetch balances for
* @returns A list of Member objects
* @see Member for the type of object returned in the list
*/
const fetchBalances = async (addressList: string[]): Promise<Member[]> => {
return Promise.all(
addressList.map(
async (address) =>
new Promise((resolve) => {
fetchBalance(address)
.then((bal: BigNumber) => {
return resolve({
address,
bal: Number(
bal.toBigInt() /
BigInt(10 ** CHAIN_METADATA.rep.nativeCurrency.decimals)
),
});
})
.catch(() =>
resolve({
address,
bal: null,
})
);
})
)
);
};
const fetchMembers = async (client: TokenVotingClient) => {
if (!votingPluginAddress) {
setLoading(false);
setError('Voting plugin address not set');
return;
}
try {
// Fetch the list of address that are members of the DAO
const addressList: string[] | null = await client.methods.getMembers(
votingPluginAddress
);
if (addressList) {
// Fetch the balance of each member
let daoMembers;
if (includeBalances) daoMembers = await fetchBalances(addressList);
else
daoMembers = addressList.map((address) => {
return {
address,
bal: null,
};
});
// Sort the members by balance, descending
daoMembers.sort((a, b) => {
if (a.bal === null) return 1;
if (b.bal === null) return -1;
return b.bal - a.bal;
});
// If a limit is set, only return that many members (with the highest balance)
if (limit) setMembers(daoMembers.splice(0, limit));
else setMembers(daoMembers);
setMemberCount(addressList.length);
setLoading(false);
setError(null);
}
} catch (e) {
console.error(e);
setLoading(false);
setError(getErrorMessage(e));
}
};
//** Set dummy data for development without querying Aragon API */
const setDummyData = () => {
setLoading(false);
setError(null);
setMembers(dummyMembers);
};
useEffect(() => {
if (useDummyData) return setDummyData();
if (!votingClient) return;
fetchMembers(votingClient);
}, [votingClient]);
/**
* Check if an address is a member of the DAO
* @param address The address to check
* @returns True if the address is a member, false otherwise
*/
const isMember = (address: string): boolean => {
return members.some((member) => member.address === address);
};
return {
loading,
error,
members,
memberCount,
isMember,
};
};