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 all 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
6 changes: 3 additions & 3 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_DAYS=
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
6 changes: 3 additions & 3 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_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
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
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
24 changes: 24 additions & 0 deletions testnet-faucet/src/lib/checkUsageLimit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { getUnixTime } from 'date-fns';

import { usageLimit } from '@/constants';

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

export default async function checkUsageLimit(receiverAddress: string): Promise<boolean> {
if (!usageLimit) {
throw new Error('NEXT_PUBLIC_USAGE_LIMIT_IN_HOURS env var undefined.');
}
let isAllowed = true;
const limitDate = getUnixTime(shiftDateBackwards(usageLimit));
const transactionResponse: PartialTransaction[] = await getLatestTransactions(1000);

transactionResponse.forEach(({ blockTime, transferDestination }) => {
if (receiverAddress == transferDestination) {
if (Number(blockTime) > limitDate) {
isAllowed = false;
}
}
});
return isAllowed;
}
36 changes: 17 additions & 19 deletions testnet-faucet/src/lib/getLatestTransactions.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
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.');
}

const latestTransactionsPath = `/accTransactions/${process.env.NEXT_PUBLIC_SENDER_ADDRESS}?limit=5&order=descending&includeRawRejectReason`;
export default async function getLatestTransactions(limit: number): Promise<PartialTransaction[]> {
const explorerApiUrl = process.env.EXPLORER_API_URL;
const senderAddress = process.env.SENDER_ADDRESS;

try {
const response = await fetch(`${process.env.NEXT_PUBLIC_EXPLORER_API_URL}${latestTransactionsPath}`);
if (!senderAddress || !explorerApiUrl) {
throw new Error('EXPLORER_API_URL, SENDER_ADDRESS env vars undefined.');
}
const latestTransactionsPath = `/accTransactions/${senderAddress}?limit=${limit}&order=descending&includeRawRejectReason`;

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

const transactionResponse: TransactionsResponse = await response.json();
return transactionResponse.transactions.map(({ blockTime, transactionHash }) => ({
blockTime,
transactionHash,
}));
} catch (error) {
console.error('Error fetching transactions:', error);
throw error;
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,
}));
}
6 changes: 3 additions & 3 deletions testnet-faucet/src/lib/getSenderAccountSigner.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { buildBasicAccountSigner } from '@concordium/web-sdk';
import { AccountSigner, buildBasicAccountSigner } from '@concordium/web-sdk';

export const getSenderAccountSigner = () => {
export default function getSenderAccountSigner(): AccountSigner {
const { SENDER_PRIVATE_KEY } = process.env;
if (!SENDER_PRIVATE_KEY) {
throw new Error('SENDER_PRIVATE_KEY env var undefined');
}
return buildBasicAccountSigner(SENDER_PRIVATE_KEY);
};
}
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
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}` });
}
}
31 changes: 31 additions & 0 deletions testnet-faucet/src/pages/api/usageLimit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { NextApiRequest, NextApiResponse } from 'next';

import checkUsageLimit from '@/lib/checkUsageLimit';

interface IBody {
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 { receiver } = req.body as IBody;

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

import { extraKeywordToVerify } from '@/constants';
import { extraKeywordToVerify, usageLimit } 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;
receiver: string;
sender: string;
XPostId: string;
}

type Data = {
Expand All @@ -23,15 +23,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 { XPostId, receiver } = req.body as IBody;

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

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

Expand All @@ -53,7 +63,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
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
Loading
Loading