Skip to content

Commit 909374a

Browse files
authored
Feature: Add rate limiter to Loopring API requests (#20)
**TL;DR:** - Introducing a Rate Limiter for requests made to the Loopring API. - Updated the error messages, and added a <Toaster /> to notify end-users of errors. **Why?** - Hitting the API Rate Limit caused a bug for users that connected with a wallet that contained a lot (800+) NFTs. This caused the Loopring API requests to return a HTTP 429 error, resulting in not all NFTs being included in the access verification.
1 parent 8e45b23 commit 909374a

File tree

7 files changed

+96
-21
lines changed

7 files changed

+96
-21
lines changed

package-lock.json

Lines changed: 35 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
{
22
"name": "loopgate",
3-
"version": "0.2.0",
3+
"version": "0.2.1",
44
"description": "Easily Token-Gate Content using Loopring Layer-2 NFTs and Pinata",
55
"author": "Geel <@0xGeel>",
66
"license": "BSD-2-Clause",
7-
"repository": "https://github.com/0xGeel/loopring-token-gating.git",
8-
"bugs": "https://github.com/0xGeel/loopring-token-gating/issues",
7+
"repository": "https://github.com/0xGeel/loopgate.git",
8+
"bugs": "https://github.com/0xGeel/loopgate/issues",
99
"homepage": "https://loopgate.netlify.app",
1010
"scripts": {
1111
"dev": "next dev",
@@ -30,6 +30,7 @@
3030
"@radix-ui/react-tabs": "^1.0.2",
3131
"@radix-ui/react-tooltip": "^1.0.3",
3232
"axios": "^1.2.3",
33+
"axios-rate-limit": "^1.3.0",
3334
"class-variance-authority": "^0.4.0",
3435
"clsx": "^1.2.1",
3536
"connectkit": "^1.1.1",
@@ -39,6 +40,7 @@
3940
"pinata-submarine": "^0.1.6",
4041
"react": "^18.2.0",
4142
"react-dom": "^18.2.0",
43+
"react-hot-toast": "^2.4.0",
4244
"siwe": "^2.1.3",
4345
"tailwind-merge": "^1.9.0",
4446
"tailwindcss-animate": "^1.0.5",

src/components/ConnectedPage/ConnectedPage.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import UnlockLink from "./UnlockLink";
55
import Spinner from "../Spinner";
66
import { useAccount } from "wagmi";
77
import axios from "axios";
8+
import toast from "react-hot-toast";
89

910
const ConnectedPage = () => {
1011
const { address } = useAccount();
@@ -19,15 +20,15 @@ const ConnectedPage = () => {
1920
setIsLoading(false);
2021
})
2122
.catch((error) => {
22-
console.log("Whoops. Something went wrong." + error);
23+
toast.error(error.request.response);
2324
setIsLoading(false);
2425
});
2526
};
2627

2728
// On render: make API calls to determine NFT holdings
28-
// 1.: GET user's Loopring ID (Loopring API)
29-
// 2.: GET user's NFTs (Loopring API)
30-
// 3.: Check config to compare NFTs and unlocks
29+
// 1.: GET user's Loopring ID (Loopring API)
30+
// 2.: GET user's NFTs (Loopring API)
31+
// 3.: Check config to compare NFTs and unlocks
3132
// 4.: GET submarined content (Pinata API)
3233

3334
useEffect(() => {

src/pages/_app.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { siwe } from "../utils/siwe";
99
import { overrides } from "../styles/ConnectKit/overrides";
1010
import NextHeadBase from "../components/SEO/NextHeadBase";
1111
import { inter, unbounded } from "../components/Fonts/Fonts";
12+
import { Toaster } from "react-hot-toast";
1213

1314
const App = ({ Component, pageProps }: AppProps) => {
1415
const [mounted, setMounted] = useState(false);
@@ -27,6 +28,20 @@ const App = ({ Component, pageProps }: AppProps) => {
2728
<main className={`${inter.variable} ${unbounded.variable} font-sans`}>
2829
<NextHeadBase />
2930
{mounted && <Component {...pageProps} />}
31+
<Toaster
32+
toastOptions={{
33+
style: {
34+
backgroundColor: "rgba(17,24,44,.8)",
35+
backdropFilter: "blur(2px)",
36+
color: "#FFFFFF",
37+
fontSize: "14px",
38+
border: "1px solid rgba(255,255,255,.2)",
39+
maxWidth: "420px",
40+
width: "100%",
41+
lineHeight: 1.5,
42+
},
43+
}}
44+
/>
3045
</main>
3146
</ConnectKitProvider>
3247
</siwe.Provider>

src/pages/api/getUserNfts.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,23 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
88

99
if (!accountId || Array.isArray(accountId[0])) {
1010
// Check if multiple or no Account IDs are specified. If so: early return.
11-
return res.status(400).json({ error: "No Loopring Account ID specified." });
11+
return res
12+
.status(400)
13+
.send(
14+
"Invalid Request: 0x address not provided. Please provide a valid 0x address and try again."
15+
);
1216
}
1317

1418
// Call Loopring API to find- and extract all user NFT IDs
1519
const allNftIds = await getAllUserNftIds(accountId);
1620

1721
return allNftIds
1822
? res.status(200).json(allNftIds)
19-
: res.status(400).json({
20-
error: "Unable to find any NFTs for the supplied Loopring Account ID",
21-
});
23+
: res
24+
.status(400)
25+
.send(
26+
"Invalid Request: Unable to find any NFTs for the specified 0x address."
27+
);
2228
};
2329

2430
export default handler;

src/pages/api/getUserUnlocks.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,30 +16,40 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
1616
const siweSesh = await siwe.getSession(req, res);
1717

1818
if (!address || Array.isArray(address)) {
19-
return res.status(400).json({ error: "No 0x address specified." });
19+
return res
20+
.status(400)
21+
.send(
22+
"0x address not provided. Please provide a valid 0x address and try again."
23+
);
2024
}
2125

2226
// Check if there is a session. Only connected users may call this endpoint.
2327
if (!siweSesh.address || siweSesh.address !== address) {
24-
return res.status(405).send({ message: "What are ye doin' in my swamp?!" });
28+
return res
29+
.status(401)
30+
.send(
31+
"You are not authorized to access this resource. Sign In With Ethereum, and try again."
32+
);
2533
}
2634

2735
// 1️⃣ Call the Loopring API to find the User's Loopring Account ID
2836
const accountId = await getUserAddress(address);
2937

3038
if (!accountId) {
31-
return res.status(400).json({
32-
error: "Could not find Loopring Account for the specified 0x address",
33-
});
39+
return res
40+
.status(400)
41+
.send(
42+
"No Loopring Account could be found for the connected 0x address. Is your L2 account activated?"
43+
);
3444
}
3545

3646
// 2️⃣ Call the Loopring API to find the NFTs held by the user
3747
const allNftIds = await getAllUserNftIds(accountId);
3848

3949
if (!allNftIds) {
4050
return res
41-
.status(400)
42-
.json({ error: "Unable to find any NFTs for the specified 0x address" });
51+
.status(404)
52+
.send("Unable to find any NFTs for the specified 0x address.");
4353
}
4454

4555
// 3️⃣ Check the user's NFT IDs against the config.ts to determine unlocks

src/utils/loopring/getAllUserNftIds.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
import axios from "axios";
2+
import rateLimit from "axios-rate-limit";
23
import { API } from "./_constants";
34
import { headerOpts, extractNfts } from "./index";
45

6+
// Loopring API accepts 5 requests per second, max.
7+
// Although this seems to fluctuate. 20 / sec
8+
const rateLimitedAxios = rateLimit(axios.create(), {
9+
maxRequests: 10,
10+
perMilliseconds: 1000,
11+
});
12+
513
const getAllUserNftIds = async (accountId: string | string[]) => {
614
// Gets all Loopring L2 NFTs for a specified Account ID
715
const LIMIT = 50; // API can handle up to 50 per call
@@ -32,7 +40,7 @@ const getAllUserNftIds = async (accountId: string | string[]) => {
3240
// Call the API for all of these ^
3341
const followUpReqs = await Promise.all(
3442
amountOfCalls.map(async (index) => {
35-
return await axios.get(
43+
return await rateLimitedAxios.get(
3644
`${API.USER_NFT_BALANCE}?accountId=${accountId}&limit=${LIMIT}&offset=${
3745
LIMIT * index
3846
}`,

0 commit comments

Comments
 (0)