Skip to content

Commit

Permalink
feat: add faucet functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
akhlopiachyi committed Apr 17, 2024
1 parent 9d0a71e commit b393261
Show file tree
Hide file tree
Showing 6 changed files with 401 additions and 15 deletions.
19 changes: 5 additions & 14 deletions app/docs/tools/faucet/faucet.mdx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Faucets } from '../../../../components/Faucets';

# Faucet

You can fund your account here.
Expand All @@ -17,17 +19,6 @@ cored keys add <name-of-the-key> --chain-id=$COREUM_CHAIN_ID
`{Chain ID}` depends on the Network, you can find value at
[network variables page](/docs/tutorials/get-started/setup-cli)

## Mainnet

You can generate your address at [wallet page](/docs/tools/wallets), and you can find the list of Coreum
Markets <a className="text-[#24D494] font-bold" href="https://coinmarketcap.com/currencies/coreum/markets" target="_blank">here</a>.

Note: Check if CEX supports withdrawal into Coreum Network beforehand.

## Testnet

{/* <Faucet chainId="coreum-testnet-1"/> */}

## Devnet

{/* <Faucet chainId="coreum-devnet-1"/> */}
<div className="mt-10">
<Faucets />
</div>
143 changes: 143 additions & 0 deletions components/Faucets/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
'use client';

import { useCallback, useEffect, useMemo, useState } from "react";
import { TabItem, Tabs } from "../Tabs";
import Link from "next/link";
import { Input } from "../Input";
import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing';
import { stringToPath } from '@cosmjs/crypto';
import axios from "axios";

const tabs = [
{ label: 'Mainnet', id: 'mainnet' },
{ label: 'Testnet', id: 'testnet' },
{ label: 'Devnet', id: 'devnet' },
];

const CONFIG = {
devnet: {
denom: 'udevcore',
tokenPrefix: 'devcore',
fundWalletURL: 'https://api.devnet-1.coreum.dev/api/faucet/v1/fund',
coreumHDPath: "m/44'/990'/0'/0/0",
explorerLink: 'https://explorer.devnet-1.coreum.dev/coreum/transactions',
},
testnet: {
denom: 'utestcore',
tokenPrefix: 'testcore',
fundWalletURL: 'https://api.testnet-1.coreum.dev/api/faucet/v1/fund',
coreumHDPath: "m/44'/990'/0'/0/0",
explorerLink: 'https://explorer.testnet-1.coreum.dev/coreum/transactions',
},
};

export const Faucets = () => {
const [currentTab, setCurrentTab] = useState<TabItem>(tabs[0]);
const [walletAddress, setWalletAddress] = useState<string>('');
const [txHash, setTxHash] = useState<string>('');

const currentConfig = useMemo(() => {
return CONFIG[currentTab.id as ('testnet' | 'devnet')] || {};
}, [currentTab.id]);

const fundWallet = useCallback(async (address: string) => {
try {
const response = await axios.post(currentConfig.fundWalletURL, {
address: address,
});

setTxHash(response.data.txHash);
} catch (error) {
console.error(error);
}
}, [currentConfig?.fundWalletURL]);

const handleRequestFunds = useCallback(async () => {
if (walletAddress.length) {
await fundWallet(walletAddress);
}
}, [fundWallet, walletAddress]);

const handleGenerateWallet = useCallback(async () => {
const wallet = await DirectSecp256k1HdWallet.generate(24, {
prefix: currentConfig.tokenPrefix,
hdPaths: [stringToPath(currentConfig.coreumHDPath)],
});

const [{ address }] = await wallet.getAccounts();

setWalletAddress(address);
await fundWallet(address);
}, [currentConfig.coreumHDPath, currentConfig.tokenPrefix, fundWallet]);

const handleChangeWalletAddress = useCallback((value: string | number) => {
setWalletAddress(typeof value === 'string' ? value : String(value));
}, []);

useEffect(() => {
setWalletAddress('');
setTxHash('');
}, [currentTab.id]);

const renderContent = useMemo(() => {
switch (currentTab.id) {
case 'mainnet':
return (
<div className="flex flex-col w-full gap-8">
<div className="text-base font-normal text-[#868991]">
You can generate your address at <Link className="text-[#25D695] font-semibold" href="#">wallet page</Link>, and you can find the list of Coreum Markets <Link className="text-[#25D695] font-semibold" href="#">here</Link>.
</div>
<div className="text-base font-normal text-[#868991]">
Note: Check if EX supports withdrawal into Coreum Network beforehand.
</div>
</div>
);
case 'testnet':
case 'devnet':
return (
<div className="flex flex-col w-full gap-4">
<div className="flex w-full items-center gap-6">
<div
className="flex overflow-hidden py-4 px-6 bg-[#0E0F10] text-base font-medium font-['space grotesk'] rounded-xl w-[280px] cursor-pointer"
onClick={handleGenerateWallet}
>
Generate Funded Wallet
</div>
<div className="flex-none text-[#9FA2AC] text-base font-['space grotesk'] font-normal">
Or
</div>
<div className="flex-1">
<Input
buttonLabel="Request Fund"
onButtonClick={handleRequestFunds}
inputName={"wallet_address"}
inputId={"wallet_address"}
inputType={"text"}
value={walletAddress}
onChange={handleChangeWalletAddress}
placeholder="Enter wallet address"
/>
</div>
</div>
{txHash.length ? (
<div className="flex w-full items-center gap-2 bg-[#0E0F10] py-4 px-6 rounded-xl text-[#5E6773] text-sm text-nowrap overflow-auto">
Tx Hash: <Link target="_blank" className="text-[#25D695] text-base" href={`${currentConfig.explorerLink}/${txHash}`}>{txHash}</Link>
</div>
) : ''}
</div>
);
default:
}
}, [currentConfig?.explorerLink, currentTab.id, handleChangeWalletAddress, handleGenerateWallet, handleRequestFunds, txHash, walletAddress]);

return (
<div className="flex flex-col w-full gap-10 overflow-hidden">
<Tabs
currentTab={currentTab}
tabs={tabs}
setCurrentTab={setCurrentTab}
/>
{renderContent}
</div>
);
};
46 changes: 46 additions & 0 deletions components/Input/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { FC } from "react";

interface InputProps {
buttonLabel: string;
onButtonClick: () => void;
inputName: string;
inputId: string;
inputType: string;
value: string | number;
onChange: (value: string | number) => void;
placeholder: string;
}

export const Input: FC<InputProps> = ({
buttonLabel,
onButtonClick,
inputName,
inputId,
inputType,
value,
onChange,
placeholder,
}) => {
return (
<div className="flex rounded-md shadow-sm border border-[#17191E] bg-[#090909] py-4 pl-5 pr-4 gap-2 sm:gap-4">
<div className="relative flex flex-grow items-stretch gap-2">
<input
type={inputType}
name={inputName}
id={inputId}
className="bg-transparent w-full rounded-none rounded-l-md text-[#eee] outline-none placeholder:text-[#5E6773] sm:text-sm sm:leading-6"
placeholder={placeholder}
value={value}
onChange={(e) => onChange(e.target.value)}
/>
</div>
<button
type="button"
onClick={onButtonClick}
className="relative inline-flex items-center gap-x-1 rounded-r-md text-sm font-semibold text-[#25D695] text-nowrap"
>
{buttonLabel}
</button>
</div>
);
}
37 changes: 37 additions & 0 deletions components/Tabs/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import classNames from "classnames";
import { FC } from "react";

export interface TabItem {
label: string;
id: string;
}

interface TabsProps {
currentTab: TabItem;
tabs: TabItem[];
setCurrentTab: (tab: TabItem) => void;
}

export const Tabs: FC<TabsProps> = ({
currentTab,
tabs,
setCurrentTab,
}) => {
return (
<nav className="flex items-center bg-[#0d110f] rounded-md p-1 w-full sm:w-fit overflow-auto" aria-label="Tabs">
{tabs.map((tab) => (
<div
key={tab.label}
onClick={() => setCurrentTab(tab)}
className={classNames(
currentTab.id === tab.id ? 'bg-sidebar-active text-[#25D695] rounded-lg font-medium' : 'text-[#5E6773] rounded-md font-normal',
"text-base px-20 py-3 font-['space grotesk'] cursor-pointer"
)}
aria-current={currentTab.id === tab.id ? 'page' : undefined}
>
{tab.label}
</div>
))}
</nav>
);
};
Loading

0 comments on commit b393261

Please sign in to comment.