-
Notifications
You must be signed in to change notification settings - Fork 2
/
tokenGate.ts
140 lines (126 loc) · 3.42 KB
/
tokenGate.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
import { ethers, Contract } from "ethers";
import { type Provider } from "@ethersproject/abstract-provider";
import { type Signer } from "@ethersproject/abstract-signer";
type BalanceOfAbi = [
{
inputs: Array<Object>;
name: string;
outputs: Array<Object>;
stateMutability: string;
type: string;
}
];
// todo: add erc20 and erc721 abis
// erc20, erc721, erc777
const balanceOfAddressAbi: BalanceOfAbi = [
{
inputs: [{ internalType: "address", name: "owner", type: "address" }],
name: "balanceOf",
outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
stateMutability: "view",
type: "function",
},
];
// erc1155
const balanceOfAddressForTokenIdAbi: BalanceOfAbi = [
{
inputs: [
{ internalType: "address", name: "", type: "address" },
{ internalType: "uint256", name: "", type: "uint256" },
],
name: "balanceOf",
outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
stateMutability: "view",
type: "function",
},
];
type TokenStandard = "erc20" | "erc721" | "erc777" | "erc1155";
type TokenGateArgs = {
balanceOfThreshold: number;
contractAddress: string;
signerOrProvider: Signer | Provider;
tokenId?: number;
tokenStandard?: TokenStandard;
userAddress: string;
};
export async function tokenGate({
balanceOfThreshold,
contractAddress,
signerOrProvider,
tokenId,
tokenStandard = "erc20",
userAddress,
}: TokenGateArgs): Promise<boolean> {
let abi: BalanceOfAbi;
if (tokenStandard === "erc1155") {
abi = balanceOfAddressForTokenIdAbi;
} else {
abi = balanceOfAddressAbi;
}
const contract = new Contract(contractAddress, abi, signerOrProvider);
try {
let balanceOf: number;
if (tokenStandard === "erc1155") {
balanceOf = await contract.balanceOf(userAddress, tokenId);
} else {
balanceOf = await contract.balanceOf(userAddress);
}
return balanceOf >= balanceOfThreshold;
} catch (e) {
console.error("tokengate::tokenGate could not query contract balance.");
console.error(e);
return false;
}
}
type RecoverAddressFromSignedMessageArgs = {
message: string;
signedMessage: string;
};
export function recoverAddressFromSignedMessage({
message,
signedMessage,
}: RecoverAddressFromSignedMessageArgs): string {
const recoveredAddress = ethers.utils.verifyMessage(message, signedMessage);
return recoveredAddress;
}
type RecoverAddressAndCheckTokenGateArgs = {
address: string;
balanceOfThreshold: number;
contractAddress: string;
message: string;
provider: Provider;
signedMessage: string;
tokenId?: number;
tokenStandard?: TokenStandard;
};
export async function recoverAddressAndCheckTokenGate({
address,
balanceOfThreshold,
contractAddress,
message,
provider,
signedMessage,
tokenId,
tokenStandard = "erc20",
}: RecoverAddressAndCheckTokenGateArgs): Promise<boolean> {
const recoveredAddress = recoverAddressFromSignedMessage({
message,
signedMessage,
});
// compare lowercased addresses in case one is checksummed and the other is not
if (address.toLowerCase() !== recoveredAddress.toLowerCase()) {
console.error(
"tokenGate::recoverAddressAndCheckTokenGate addresses do not match"
);
return false;
}
const isEnabled = await tokenGate({
balanceOfThreshold,
contractAddress,
signerOrProvider: provider,
tokenId,
tokenStandard,
userAddress: recoveredAddress,
});
return isEnabled;
}