Skip to content

Commit

Permalink
update: requested changes
Browse files Browse the repository at this point in the history
  • Loading branch information
benya7 authored and DOBEN committed Aug 11, 2024
1 parent e6772fb commit 31cfc6c
Show file tree
Hide file tree
Showing 10 changed files with 147 additions and 104 deletions.
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
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;

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;

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;

0 comments on commit 31cfc6c

Please sign in to comment.