Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update usage limit #98

Merged
merged 10 commits into from
Aug 11, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion testnet-faucet/.env.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
NEXT_PUBLIC_EXPLORER_API_URL=
NEXT_PUBLIC_EXPLORER_URL=
NEXT_PUBLIC_SENDER_ADDRESS=
NEXT_PUBLIC_USAGE_LIMIT_IN_DAYS=
NEXT_PUBLIC_USAGE_LIMIT_IN_HOURS=
NEXT_PUBLIC_CLOUDFLARE_TURNSTILE_SITE_KEY=
NODE_URL=
NODE_PORT=
Expand Down
2 changes: 1 addition & 1 deletion testnet-faucet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Example enviroment variable values
NEXT_PUBLIC_EXPLORER_API_URL=https://wallet-proxy.testnet.concordium.com/v1
NEXT_PUBLIC_EXPLORER_URL=https://ccdexplorer.io/
NEXT_PUBLIC_SENDER_ADDRESS=4eDtVqZrkmcNEFziEMSs8S2anvkH5KnsYK4MhwedwGWK1pmjZe
NEXT_PUBLIC_USAGE_LIMIT_IN_DAYS=1
NEXT_PUBLIC_USAGE_LIMIT_IN_HOURS=1
benya7 marked this conversation as resolved.
Show resolved Hide resolved
NEXT_PUBLIC_CLOUDFLARE_TURNSTILE_SITE_KEY=3x00000000000000000000FF
NODE_URL=node.testnet.concordium.com
NODE_PORT=20000
Expand Down
4 changes: 3 additions & 1 deletion testnet-faucet/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export const TWEET_TEMPLATE =
'Excited to use the testnet faucet! 🚀 Requesting CCD tokens to power my blockchain experiments. Check it out! #Concordium #Blockchain #Testnet';

export const usageLimit = Number(process.env.NEXT_PUBLIC_USAGE_LIMIT_IN_HOURS!);

export const FAQ = [
{
question: 'What do I need to use the faucet?',
Expand All @@ -13,7 +15,7 @@ export const FAQ = [
},
{
question: 'Is there any usage limit?',
response: `Yes, currently you can use the faucet once every ${Number(process.env.NEXT_PUBLIC_USAGE_LIMIT_IN_DAYS) * 24} hours`,
response: `Yes, currently you can use the faucet once every ${usageLimit} ${usageLimit > 1 ? 'hours' : 'hour'}.`,
},
];

Expand Down
38 changes: 0 additions & 38 deletions testnet-faucet/src/lib/isWithinUsageLimit.ts

This file was deleted.

6 changes: 4 additions & 2 deletions testnet-faucet/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ export const extractITweetdFromUrl = (url: string): string | null => {
return null;
}
};

export const formatTimestamp = (timestamp: number): string => format(fromUnixTime(timestamp), 'yyyy-MM-dd HH:mm:ss');
export const shiftDateBackwards = (days: number) => {

export const shiftDateBackwards = (hours: number) => {
const shiftedDate = new Date();
shiftedDate.setDate(shiftedDate.getDate() - days);
shiftedDate.setHours(shiftedDate.getHours() - hours);
return shiftedDate;
};

Expand Down
57 changes: 57 additions & 0 deletions testnet-faucet/src/pages/api/checkUsageLimit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { getUnixTime } from 'date-fns';
import type { NextApiRequest, NextApiResponse } from 'next';

import { shiftDateBackwards } from '@/lib/utils';

interface IBody {
receiver: string;
}

type Data = {
isAllowed?: boolean;
error?: string;
};

export default async function handler(req: NextApiRequest, res: NextApiResponse<Data>) {
benya7 marked this conversation as resolved.
Show resolved Hide resolved
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method Not Allowed. Please use POST.' });
}
const senderAddress = process.env.NEXT_PUBLIC_SENDER_ADDRESS;
const explorerApiUrl = process.env.NEXT_PUBLIC_EXPLORER_API_URL;
const usageLimit = process.env.NEXT_PUBLIC_USAGE_LIMIT_IN_HOURS;

if (!senderAddress || !explorerApiUrl || !usageLimit) {
throw new Error(
'NEXT_PUBLIC_EXPLORER_API_URL, NEXT_PUBLIC_SENDER_ADDRESS, NEXT_PUBLIC_USAGE_LIMIT_IN_HOURS env vars undefined.',
benya7 marked this conversation as resolved.
Show resolved Hide resolved
);
}
const { receiver } = req.body as IBody;

if (!receiver) {
return res.status(400).json({
error: 'Missing parameters. Please provide receiver param.',
});
}
const latestTransactionsPath = `/accTransactions/${receiver}?limit=1000&order=descending&includeRawRejectReason`;
benya7 marked this conversation as resolved.
Show resolved Hide resolved
try {
const response = await fetch(`${explorerApiUrl}${latestTransactionsPath}`);

if (!response.ok) {
throw new Error(`Failed to fetch transactions: ${response.statusText}`);
}
let isAllowed = true;
const limitDate = getUnixTime(shiftDateBackwards(Number(process.env.NEXT_PUBLIC_USAGE_LIMIT_IN_HOURS)));
const transactionResponse: TransactionsResponse = await response.json();

transactionResponse.transactions.forEach(({ blockTime, origin }) => {
if (senderAddress == origin.address) {
if (Number(blockTime) > limitDate) {
isAllowed = false;
}
}
});
return res.status(200).json({ isAllowed });
} catch (e) {
return res.status(500).json({ error: `An unexpected error has occurred: ${e}` });
}
}
94 changes: 58 additions & 36 deletions testnet-faucet/src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@ import Image from 'next/image';
import { ErrorAlert } from '@/components/ErrorAlert';
import { SingleInputForm } from '@/components/SingleInpuForm';
import { Step } from '@/components/Step';
import { FAQ, TWEET_TEMPLATE } from '@/constants';
import { FAQ, TWEET_TEMPLATE, usageLimit } from '@/constants';
import getLatestTransactions from '@/lib/getLatestTransactions';
import isWithinUsageLimit from '@/lib/isWithinUsageLimit';
import { extractITweetdFromUrl, formatTimestamp, formatTxHash } from '@/lib/utils';

import concordiumLogo from '../../public/concordium-logo-back.svg';
Expand All @@ -33,6 +32,46 @@ const IBMPlexMono = IBM_Plex_Mono({
variable: '--font-ibm-plex-mono',
});

const validateAndClaim = async (XPostId: string | undefined, receiver: string) => {
try {
const response = await fetch('/api/validateAndClaim', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
XPostId,
receiver,
sender: process.env.NEXT_PUBLIC_SENDER_ADDRESS,
benya7 marked this conversation as resolved.
Show resolved Hide resolved
}),
});

const data = await response.json();

return { ok: response.ok, data };
} catch (error) {
throw new Error('Network error. Please check your connection.');
}
};

const checkUsageLimit = async (receiver: string) => {
try {
const response = await fetch('/api/checkUsageLimit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ receiver }),
});

const data = await response.json();

return { ok: response.ok, data };
} catch (error) {
throw new Error('Network error. Please check your connection.');
}
};

export default function Home() {
const [latestTransactions, setLatestTransactions] = useState<PartialTransaction[]>([]);
const [address, setAddress] = useState<string>('');
Expand All @@ -41,6 +80,7 @@ export default function Home() {
const [tweetPostedUrl, setTweetPostedUrl] = useState('');
const [XPostId, SetXPostId] = useState<string | undefined>();

const [isAddressValid, setIsAddressValid] = useState<boolean>(false);
const [isValidTweetUrl, setIsValidTweetUrl] = useState<boolean | undefined>();
const [isValidVerification, setIsValidVerification] = useState<boolean | undefined>();
const [isVerifyLoading, setIsVerifyLoading] = useState<boolean>(false);
Expand Down Expand Up @@ -74,33 +114,11 @@ export default function Home() {
'width=500,height=500',
);

const validateAndClaim = async () => {
try {
const response = await fetch('/api/validateAndClaim', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
XPostId,
sender: process.env.NEXT_PUBLIC_SENDER_ADDRESS,
receiver: address,
}),
});

const data = await response.json();

return { ok: response.ok, data };
} catch (error) {
throw new Error('Network error. Please check your connection.');
}
};

const handleVerifyTweetAndSendTokens = async () => {
setTurnstileOpen(false);
setIsVerifyLoading(true);
try {
const response = await validateAndClaim();
const response = await validateAndClaim(XPostId, address);

if (response.ok) {
setIsValidVerification(true);
Expand All @@ -118,27 +136,28 @@ export default function Home() {
};

useEffect(() => {
setAddressValidationError(undefined);
setIsAddressValid(false);
if (!address) {
setAddressValidationError(undefined);
return;
}
const checkUsageLimit = async () => {
const isWithinUsageLimit = async () => {
try {
const isAllowed = await isWithinUsageLimit(address);

if (!isAllowed) {
const { ok, data } = await checkUsageLimit(address);
if (ok && !data.isAllowed) {
setAddressValidationError(
`You already get tokens in the last ${Number(process.env.NEXT_PUBLIC_USAGE_LIMIT_IN_DAYS) * 24} hours. Please try again later.`,
`You already get tokens in the last ${usageLimit} ${usageLimit > 1 ? 'hours' : 'hour'}. Please try again later.`,
);
return;
}
setIsAddressValid(true);
} catch (error) {
console.log('Error on checkUsageLimit', error);
console.log('Error on checkUsageLimit:', error);
}
};
try {
AccountAddress.fromBase58(address);
setAddressValidationError(undefined);
checkUsageLimit();
isWithinUsageLimit();
} catch (error) {
setAddressValidationError('Invalid address. Please insert a valid one.');
}
Expand Down Expand Up @@ -178,7 +197,7 @@ export default function Home() {
</div>
<main className="flex flex-col items-center justify-between py-8 md:pt-12 md:pb-28 w-full">
<p className="text-center text-sm md:text-base mb-4 md:mb-8 px-10">
{`Get Testnet CDDs every ${Number(process.env.NEXT_PUBLIC_USAGE_LIMIT_IN_DAYS) * 24} hours for testing your dApps!`}
{`Get Testnet CDDs every ${usageLimit} ${usageLimit > 1 ? 'hours' : 'hour'} for testing your dApps!`}
</p>
<div className="flex flex-col md:flex-row justify-center items-center md:items-start w-full text-sm md:text-base px-4 gap-6 lg:gap-12">
<div
Expand All @@ -194,7 +213,10 @@ export default function Home() {
submitButtonText="Share on X"
inputDisabled={Boolean(tweetPostedUrl)}
submitButtonDisabled={
!address || (address && Boolean(addressValidationError)) || Boolean(tweetPostedUrl)
!address ||
(address && Boolean(addressValidationError)) ||
Boolean(tweetPostedUrl) ||
!isAddressValid
}
>
{addressValidationError && (
Expand Down
Loading