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

add: testnet-faucet project #93

Merged
merged 7 commits into from
Aug 5, 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
3 changes: 3 additions & 0 deletions .github/workflows/yarn-build-fmt-lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ jobs:
- sponsoredTransactions/frontend
- sponsoredTransactionsAuction/frontend
- trackAndTrace/frontend
- testnet-faucet

steps:
- name: Checkout
Expand Down Expand Up @@ -69,6 +70,7 @@ jobs:
- sponsoredTransactions/frontend
- sponsoredTransactionsAuction/frontend
- trackAndTrace/frontend
- testnet-faucet

steps:
- name: Checkout
Expand Down Expand Up @@ -112,6 +114,7 @@ jobs:
- sponsoredTransactions/frontend
- sponsoredTransactionsAuction/frontend
- trackAndTrace/frontend
- testnet-faucet

steps:
- name: Checkout
Expand Down
9 changes: 9 additions & 0 deletions testnet-faucet/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
NEXT_PUBLIC_EXPLORER_API_URL=
NEXT_PUBLIC_EXPLORER_URL=
NEXT_PUBLIC_SENDER_ADDRESS=
NEXT_PUBLIC_USAGE_LIMIT_IN_DAYS=
NEXT_PUBLIC_CLOUDFLARE_TURNSTILE_SITE_KEY=
NODE_URL=
NODE_PORT=
CCD_DEFAULT_AMOUNT=
SENDER_PRIVATE_KEY=
25 changes: 25 additions & 0 deletions testnet-faucet/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"extends": ["next/core-web-vitals", "prettier"],
"plugins": ["import"],
"rules": {
"import/order": [
"error",
{
"groups": ["builtin", "external", "internal"],
"pathGroups": [
{
"pattern": "react",
"group": "external",
"position": "before"
}
],
"pathGroupsExcludedImportTypes": ["react"],
"newlines-between": "always",
"alphabetize": {
"order": "asc",
"caseInsensitive": true
}
}
]
}
}
36 changes: 36 additions & 0 deletions testnet-faucet/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
4 changes: 4 additions & 0 deletions testnet-faucet/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.yarn/
.next/
docs/
node_modules/
25 changes: 25 additions & 0 deletions testnet-faucet/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# concordium-testnet-faucet

## Development

Create the .env.local file and fill up the variables.

```bash
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_CLOUDFLARE_TURNSTILE_SITE_KEY=3x00000000000000000000FF
NODE_URL=node.testnet.concordium.com
NODE_PORT=20000
CCD_DEFAULT_AMOUNT=1
SENDER_PRIVATE_KEY=12...34
```

Note: The `3x00000000000000000000FF` value in the `NEXT_PUBLIC_CLOUDFLARE_TURNSTILE_SITE_KEY` is a site key provided by Cloudflare for testing purposes; it works fine locally. For setting up the Cloudflare Turnstile service in production, please refer to this [guide](docs/turnstile/SETUP.md).
35 changes: 35 additions & 0 deletions testnet-faucet/docs/turnstile/SETUP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Cloudflare Turnstile setup

The purpose of this guide is to show how to configure the production domain in Vercel and the Cloudflare Turnstile service.

1. Go to the project in Vercel, then Settings/Domains
![step1](images/step1.png)

2. Fill in your domain and click Add.
![step2](images/step2.png)

3. Note the table values, To use in the next step.
![step3](images/step3.png)

4. Go to the domain management panel, where you or your team has the domain registered, and then DNS Settings. And add a new CNAME record. Fill up with the values from the previous step.
Name -> Host and Value -> Value .
![step4](images/step4.png)
![step4b](images/step4b.png)
![step4c](images/step4c.png)

5. Go back to the project in Settings/Vercel Domains and wait for it to look like this.
![step5](images/step5.png)

6. In your Cloudflare dashboard. Go to Turnstile and fill it in as follows.
![step6](images/step6.png)

7. Click on create and copy the sitekey
![step7](images/step7.png)

8. Go to the project on Vercel, then Settings / Environment variables and update the variable NEXT_PUBLIC_CLOUDFLARE_TURNSTILE_SITE_KEY
![step8](images/step8.png)

9. Go to Deployments and click on the three points of the last deployment and then click on redeploy.
![step9](images/step9.png)

That's it, the produccion domain and the Cloudfare Human Verification is ready now. 🎉
Binary file added testnet-faucet/docs/turnstile/images/step1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added testnet-faucet/docs/turnstile/images/step2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added testnet-faucet/docs/turnstile/images/step3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added testnet-faucet/docs/turnstile/images/step4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added testnet-faucet/docs/turnstile/images/step4b.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added testnet-faucet/docs/turnstile/images/step4c.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added testnet-faucet/docs/turnstile/images/step5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added testnet-faucet/docs/turnstile/images/step6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added testnet-faucet/docs/turnstile/images/step7.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added testnet-faucet/docs/turnstile/images/step8.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added testnet-faucet/docs/turnstile/images/step9.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions testnet-faucet/next.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
};

export default nextConfig;
35 changes: 35 additions & 0 deletions testnet-faucet/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "concordium-testnet-faucet",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@concordium/web-sdk": "^7.5.0",
"@headlessui/react": "^2.1.2",
"@marsidev/react-turnstile": "^0.7.2",
"date-fns": "^3.6.0",
"next": "14.2.5",
"react": "^18",
"react-dom": "^18",
"rettiwt-api": "^3.1.1",
"usehooks-ts": "^3.1.0"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "14.2.5",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1",
"postcss": "^8",
"prettier": "^3.3.3",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
}
8 changes: 8 additions & 0 deletions testnet-faucet/postcss.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
},
};

export default config;
3 changes: 3 additions & 0 deletions testnet-faucet/public/concordium-logo-back.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added testnet-faucet/public/favicon.ico
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions testnet-faucet/src/components/ErrorAlert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
interface IProps {
errorText: string;
onClose: () => void;
}

export const ErrorAlert = ({ errorText, onClose }: IProps) => (
<div className="fixed inset-x-0 bottom-0">
<div className="relative bg-[--red-light] mx-auto w-[90%] sm:w-3/4 max-w-md mb-10 min-h-24 max-h-40 rounded-md p-6 text-white">
<button className="absolute top-2 right-4 text-lg" onClick={onClose}>
x
</button>
<p className="text-center">{errorText}</p>
</div>
</div>
);
40 changes: 40 additions & 0 deletions testnet-faucet/src/components/SingleInpuForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
interface IProps {
inputValue: string;
handleInputValue: (e: React.ChangeEvent<HTMLInputElement>) => void;
handleSubmitButton: () => void;
inputPlaceHolder: string;
submitButtonText: string;
inputDisabled?: boolean;
submitButtonDisabled?: boolean;
children?: React.ReactNode;
}

export const SingleInputForm = ({
inputValue,
handleInputValue,
handleSubmitButton,
inputPlaceHolder,
submitButtonText,
inputDisabled,
submitButtonDisabled,
children,
}: IProps) => (
<div className="flex flex-col w-full max-w-xl gap-3">
<input
type="text"
placeholder={inputPlaceHolder}
value={inputValue}
className="h-10 md:h-11 w-full text-xs sm:text-sm px-2 border border-[--dark-blue] rounded-md disabled:text-gray-400 focus-visible:outline-none"
disabled={inputDisabled}
onChange={handleInputValue}
/>
{children}
<button
className="bg-[--blue] px-2 py-1 h-9 md:h-10 font-semibold text-white rounded-md hover:bg-[--blue-light] disabled:bg-gray-300 disabled:cursor-default w-28 self-center sm:w-36 text-nowrap mb-6"
onClick={handleSubmitButton}
disabled={submitButtonDisabled}
>
{submitButtonText}
</button>
</div>
);
5 changes: 5 additions & 0 deletions testnet-faucet/src/components/Step.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const Step = ({ step }: { step: number }) => (
<div className="border border-[--dark-blue] rounded-full h-8 w-8 sm:h-9 sm:w-9 flex items-center justify-center mb-4">
<p className="font-semibold">{step}</p>
</div>
);
20 changes: 20 additions & 0 deletions testnet-faucet/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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 FAQ = [
{
question: 'What do I need to use the faucet?',
response: 'You need a CDD testnet wallet address and an X account.',
},
{
question: 'How does it work?',
response:
'Step 1. Paste your wallet address and press the Share on X button.\n Step 2. Copy the link from your X post and paste it into input, then press the Verify button.\n Step 3. A dialog will open to verify that you are human, if requested, mark the checkbox.\n After verifying that you are human, your X Post will be verified and if it is successful, the tokens will be transferred to your wallet',
},
{
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`,
},
];

export const extraKeywordToVerify = 'Concordium';
39 changes: 39 additions & 0 deletions testnet-faucet/src/lib/createAccountTrasantion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {
AccountAddress,
AccountTransaction,
AccountTransactionHeader,
AccountTransactionType,
CcdAmount,
NextAccountNonce,
TransactionExpiry,
} from '@concordium/web-sdk';
import { ConcordiumGRPCNodeClient } from '@concordium/web-sdk/nodejs';

export default async function createAccountTransaction(
client: ConcordiumGRPCNodeClient,
sender: string,
receiver: string,
): Promise<AccountTransaction> {
const { CCD_DEFAULT_AMOUNT } = process.env;
if (!CCD_DEFAULT_AMOUNT) {
throw new Error('CCD_DEFAULT_AMOUNT env var undefined');
}
const senderAddress = AccountAddress.fromBase58(sender);
const nextNonce: NextAccountNonce = await client.getNextAccountNonce(senderAddress);
const toAddress = AccountAddress.fromBase58(receiver);

const header: AccountTransactionHeader = {
expiry: TransactionExpiry.futureMinutes(60),
nonce: nextNonce.nonce,
sender: senderAddress,
};
const simpleTransfer = {
amount: CcdAmount.fromMicroCcd(CCD_DEFAULT_AMOUNT),
toAddress,
};
return {
header: header,
payload: simpleTransfer,
type: AccountTransactionType.Transfer,
};
}
9 changes: 9 additions & 0 deletions testnet-faucet/src/lib/createGPRCClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ConcordiumGRPCNodeClient, credentials } from '@concordium/web-sdk/nodejs';

export default function createGRPCNodeClient(): ConcordiumGRPCNodeClient {
const { NODE_PORT, NODE_URL } = process.env;
if (!NODE_PORT || !NODE_URL) {
throw new Error('NDDE_PORT or NODE_URL env vars not defined.');
}
return new ConcordiumGRPCNodeClient(NODE_URL as string, Number(NODE_PORT), credentials.createInsecure());
}
24 changes: 24 additions & 0 deletions testnet-faucet/src/lib/getLatestTransactions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +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.');
}

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;
}
}
9 changes: 9 additions & 0 deletions testnet-faucet/src/lib/getSenderAccountSigner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { buildBasicAccountSigner } from '@concordium/web-sdk';

export const getSenderAccountSigner = () => {
const { SENDER_PRIVATE_KEY } = process.env;
if (!SENDER_PRIVATE_KEY) {
throw new Error('SENDER_PRIVATE_KEY env var undefined');
}
return buildBasicAccountSigner(SENDER_PRIVATE_KEY);
};
Loading
Loading