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 1 commit
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
4 changes: 2 additions & 2 deletions testnet-faucet/.env.example
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
NEXT_PUBLIC_EXPLORER_API_URL=
NEXT_PUBLIC_EXPLORER_URL=
NEXT_PUBLIC_SENDER_ADDRESS=
NEXT_PUBLIC_USAGE_LIMIT_IN_HOURS=
NEXT_PUBLIC_CLOUDFLARE_TURNSTILE_SITE_KEY=
EXPLORER_API_URL=
SENDER_ADDRESS=
NODE_URL=
NODE_PORT=
CCD_DEFAULT_AMOUNT=
Expand Down
4 changes: 2 additions & 2 deletions testnet-faucet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ cp .env.example .env.local
Example enviroment variable values

```bash
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_HOURS=1
benya7 marked this conversation as resolved.
Show resolved Hide resolved
NEXT_PUBLIC_CLOUDFLARE_TURNSTILE_SITE_KEY=3x00000000000000000000FF
EXPLORER_API_URL=https://wallet-proxy.testnet.concordium.com/v1
SENDER_ADDRESS=4eDtVqZrkmcNEFziEMSs8S2anvkH5KnsYK4MhwedwGWK1pmjZe
NODE_URL=node.testnet.concordium.com
NODE_PORT=20000
CCD_DEFAULT_AMOUNT=1
Expand Down
19 changes: 19 additions & 0 deletions testnet-faucet/src/lib/checkUsageLimit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { getUnixTime } from "date-fns";

import getLatestTransactions from "./getLatestTransactions";
import { shiftDateBackwards } from "./utils";

export default async function checkUsageLimit(hoursLimit: number, receiverAddress: string): Promise<boolean> {
let isAllowed = true;
const limitDate = getUnixTime(shiftDateBackwards(hoursLimit));
const transactionResponse: PartialTransaction[] = await getLatestTransactions(1000);

transactionResponse.forEach(({ blockTime, transferDestination }) => {
if (receiverAddress == transferDestination ) {
if (Number(blockTime) > limitDate) {
isAllowed = false;
}
}
});
return isAllowed
}
42 changes: 21 additions & 21 deletions testnet-faucet/src/lib/getLatestTransactions.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
export default async function getLatestTransactions(): Promise<PartialTransaction[]> {
if (!process.env.NEXT_PUBLIC_EXPLORER_API_URL || !process.env.NEXT_PUBLIC_SENDER_ADDRESS) {
throw new Error('NEXT_PUBLIC_EXPLORER_API_URL or NEXT_PUBLIC_SENDER_ADDRESS env vars undefined.');
export default async function getLatestTransactions(limit: number): Promise<PartialTransaction[]> {
const explorerApiUrl = process.env.EXPLORER_API_URL;
const senderAddress = process.env.SENDER_ADDRESS;

if (!senderAddress || !explorerApiUrl) {
throw new Error(
'EXPLORER_API_URL, SENDER_ADDRESS env vars undefined.',
);
}

const latestTransactionsPath = `/accTransactions/${process.env.NEXT_PUBLIC_SENDER_ADDRESS}?limit=5&order=descending&includeRawRejectReason`;

try {
const response = await fetch(`${process.env.NEXT_PUBLIC_EXPLORER_API_URL}${latestTransactionsPath}`);

if (!response.ok) {
throw new Error(`Failed to fetch transactions: ${response.statusText}`);
}

const transactionResponse: TransactionsResponse = await response.json();
return transactionResponse.transactions.map(({ blockTime, transactionHash }) => ({
blockTime,
transactionHash,
}));
} catch (error) {
console.error('Error fetching transactions:', error);
throw error;
const latestTransactionsPath = `/accTransactions/${senderAddress}?limit=${limit}&order=descending&includeRawRejectReason`;

const response = await fetch(`${explorerApiUrl}${latestTransactionsPath}`);

if (!response.ok) {
throw new Error(`Failed to fetch transactions: ${response.statusText}`);
}

const transactionResponse: TransactionsResponse = await response.json();
return transactionResponse.transactions.map(({ blockTime, transactionHash, details }) => ({
blockTime,
transactionHash,
transferDestination: details.transferDestination
}));
}
57 changes: 0 additions & 57 deletions testnet-faucet/src/pages/api/checkUsageLimit.ts

This file was deleted.

20 changes: 20 additions & 0 deletions testnet-faucet/src/pages/api/latestTransactions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { NextApiRequest, NextApiResponse } from 'next';

import getLatestTransactions from '@/lib/getLatestTransactions';

type Data = {
transactions?: PartialTransaction[];
error?: string;
};

export default async function handler(req: NextApiRequest, res: NextApiResponse<Data>) {
if (req.method !== 'GET') {
return res.status(405).json({ error: 'Method Not Allowed. Please use GET.' });
}
try {
const transactions = await getLatestTransactions(5)
return res.status(200).json({ transactions });
} catch (e) {
return res.status(500).json({ error: `An unexpected error has occurred: ${e}` });
}
}
32 changes: 32 additions & 0 deletions testnet-faucet/src/pages/api/usageLimit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { NextApiRequest, NextApiResponse } from 'next';

import checkUsageLimit from '@/lib/checkUsageLimit';

interface IBody {
hoursLimit: number;
receiver: string;
}

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

export default async function handler(req: NextApiRequest, res: NextApiResponse<Data>) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method Not Allowed. Please use POST.' });
}
const { hoursLimit, receiver } = req.body as IBody;
benya7 marked this conversation as resolved.
Show resolved Hide resolved

if (!hoursLimit || !receiver) {
return res.status(400).json({
error: 'Missing parameters. Please provide hoursLimit and receiver params.',
});
}
try {
const isAllowed = await checkUsageLimit(hoursLimit, receiver)
return res.status(200).json({ isAllowed });
} catch (e) {
return res.status(500).json({ error: `An unexpected error has occurred: ${e}` });
}
}
29 changes: 20 additions & 9 deletions testnet-faucet/src/pages/api/validateAndClaim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { Rettiwt } from 'rettiwt-api';

import { extraKeywordToVerify } from '@/constants';
import checkUsageLimit from '@/lib/checkUsageLimit';
import createAccountTransaction from '@/lib/createAccountTrasantion';
import createGRPCNodeClient from '@/lib/createGPRCClient';
import { getSenderAccountSigner } from '@/lib/getSenderAccountSigner';
import getSenderAccountSigner from '@/lib/getSenderAccountSigner';

interface IBody {
XPostId: string;
hoursLimit: number;
receiver: string;
sender: string;
XPostId: string;
}

type Data = {
Expand All @@ -23,15 +24,25 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
return res.status(405).json({ error: 'Method Not Allowed. Please use POST.' });
}

const { XPostId, receiver, sender } = req.body as IBody;
const senderAddress = process.env.SENDER_ADDRESS;
if (!senderAddress) {
throw new Error('SENDER_ADDRESS env vars undefined.');
}

const { hoursLimit, XPostId, receiver } = req.body as IBody;
benya7 marked this conversation as resolved.
Show resolved Hide resolved

if (!XPostId || !receiver || !sender) {
if (!hoursLimit || !XPostId || !receiver) {
return res.status(400).json({
error: 'Missing parameters. Please provide XPostId, receiver and sender.',
error: 'Missing parameters. Please provide hoursLimit XPostId, and receiver params.',
});
}

try {
const isAllowed = await checkUsageLimit(hoursLimit, receiver)
if (!isAllowed) {
return res.status(401).json({
error: `You already get tokens in the last ${hoursLimit} ${hoursLimit > 1 ? 'hours' : 'hour'}. Please try again later.`,
});
}
const rettiwt = new Rettiwt();
const response = await rettiwt.tweet.details(XPostId);

Expand All @@ -49,11 +60,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
error: 'X Post verification failed. Please make sure you do not modify the template text and that your address is present.',
});
}

const client = createGRPCNodeClient();
const signer = getSenderAccountSigner();

const accountTransaction = await createAccountTransaction(client, sender, receiver);
const accountTransaction = await createAccountTransaction(client, senderAddress, receiver);
const signature: AccountTransactionSignature = await signTransaction(accountTransaction, signer);

const transactionHash = await client.sendAccountTransaction(accountTransaction, signature);
Expand Down
42 changes: 30 additions & 12 deletions testnet-faucet/src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import { ErrorAlert } from '@/components/ErrorAlert';
import { SingleInputForm } from '@/components/SingleInpuForm';
import { Step } from '@/components/Step';
import { FAQ, TWEET_TEMPLATE, usageLimit } from '@/constants';
import getLatestTransactions from '@/lib/getLatestTransactions';
import { extractITweetdFromUrl, formatTimestamp, formatTxHash } from '@/lib/utils';

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

const validateAndClaim = async (XPostId: string | undefined, receiver: string) => {
const getLatestTransactions = async () => {
try {
const response = await fetch('/api/latestTransactions', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
}
});
const data = await response.json();

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

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

Expand All @@ -54,14 +69,14 @@ const validateAndClaim = async (XPostId: string | undefined, receiver: string) =
}
};

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

const data = await response.json();
Expand Down Expand Up @@ -118,9 +133,9 @@ export default function Home() {
setTurnstileOpen(false);
setIsVerifyLoading(true);
try {
const response = await validateAndClaim(XPostId, address);
const response = await validateAndClaim(usageLimit, XPostId, address);

if (response.ok) {
if (response.ok && !response.data.error) {
setIsValidVerification(true);
await new Promise((resolve) => setTimeout(resolve, 15000));
setTransactionHash(response.data.transactionHash);
Expand All @@ -143,7 +158,7 @@ export default function Home() {
}
const isWithinUsageLimit = async () => {
try {
const { ok, data } = await checkUsageLimit(address);
const { ok, data } = await checkUsageLimit(usageLimit, address);
if (ok && !data.isAllowed) {
setAddressValidationError(
`You already get tokens in the last ${usageLimit} ${usageLimit > 1 ? 'hours' : 'hour'}. Please try again later.`,
Expand Down Expand Up @@ -175,8 +190,11 @@ export default function Home() {
useEffect(() => {
const fetchTransactions = async () => {
try {
const transactions = await getLatestTransactions();
setLatestTransactions(transactions);
const { ok, data } = await getLatestTransactions();
if (ok && !data.transactions) {
return;
}
setLatestTransactions(data.transactions);
} catch (error) {
console.error('Error fetching transactions:', error);
}
Expand Down
2 changes: 1 addition & 1 deletion testnet-faucet/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ interface TransactionsResponse {
transactions: Transaction[];
}

type PartialTransaction = Pick<Transaction, 'blockTime' | 'transactionHash'>;
type PartialTransaction = Pick<Transaction, 'blockTime' | 'transactionHash'> & { transferDestination: string };

type CloudfareWidgetStatus = 'solved' | 'error' | 'expired' | null;