Skip to content

Commit

Permalink
Merge pull request #2359 from zeitgeistpm/tr-claim-page
Browse files Browse the repository at this point in the history
Claim page
  • Loading branch information
Robiquet authored Mar 28, 2024
2 parents 4547b5a + 19bde50 commit de34e98
Show file tree
Hide file tree
Showing 6 changed files with 7,667 additions and 10 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,6 @@ NEXT_PUBLIC_WHITELISTED_TRUSTED_CREATORS=["xxxxxx"]
IPFS_NODE_BASIC_AUTH_USERNAME=xxxxx
IPFS_NODE_BASIC_AUTH_PASSWORD=xxxxx

NEXT_PUBLIC_SHOW_AIRDROP=true


39 changes: 38 additions & 1 deletion components/top-bar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,11 @@ const TopBar = () => {
</div>
<MarketSearch />
<div className="center relative ml-auto gap-3">
<GetTokensButton />
{process.env.NEXT_PUBLIC_SHOW_AIRDROP !== "true" ? (
<GetTokensButton />
) : (
<AirdropButton />
)}
<AccountButton />
<Alerts />
</div>
Expand Down Expand Up @@ -265,6 +269,39 @@ const GetTokensButton = () => {
);
};

const AirdropButton = () => {
return (
<Transition
as={Fragment}
show={true}
enter="transition-all duration-250"
enterFrom="opacity-0 scale-90"
enterTo="opacity-100 scale-100"
leave="transition-all duration-250"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-90"
>
<Link
className="group relative hidden h-11 overflow-hidden rounded-md p-0.5 sm:block"
href="/claim"
>
<div
className="absolute left-0 top-0 z-10 h-full w-full group-hover:-left-6 group-hover:-top-6 group-hover:h-[150%] group-hover:w-[150%] group-hover:animate-spin"
style={{
background:
"linear-gradient(180deg, #FF00E6 0%, #F36464 50%, #04C3FF 100%)",
}}
/>
<div className="relative z-20 block h-full sm:w-[100px] ">
<button className="center h-full w-full rounded-md bg-black text-white">
Airdrop!
</button>
</div>
</Link>
</Transition>
);
};

const CategoriesMenu = ({ onSelect }: { onSelect: () => void }) => {
const { data: counts } = useCategoryCounts();

Expand Down
20 changes: 11 additions & 9 deletions lib/state/polkadot-api.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { ApiPromise, WsProvider } from "@polkadot/api";
import { atom, useAtom } from "jotai";
import { loadable } from "jotai/utils";
import { ChainName, CHAINS } from "lib/constants/chains";
import { useSdkv2 } from "lib/hooks/useSdkv2";
import { environment } from "lib/constants";

const endpoints = [
"wss://rpc.polkadot.io",
"wss://polkadot-rpc.dwellir.com",
"wss://polkadot.public.curie.radiumblock.co/ws",
"wss://1rpc.io/dot",
"wss://rpc-polkadot.luckyfriday.io",
];
const endpoints =
environment === "production"
? [
"wss://rpc.polkadot.io",
"wss://polkadot-rpc.dwellir.com",
"wss://polkadot.public.curie.radiumblock.co/ws",
"wss://1rpc.io/dot",
"wss://rpc-polkadot.luckyfriday.io",
]
: ["wss://rococo-rpc.polkadot.io"];

const polkadotApiAtom = loadable(
atom(async () => {
Expand Down
284 changes: 284 additions & 0 deletions pages/claim.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
import { decodeAddress, encodeAddress } from "@polkadot/keyring";
import { useNotifications } from "lib/state/notifications";
import { usePolkadotApi } from "lib/state/polkadot-api";
import { useWallet } from "lib/state/wallet";
import { extrinsicCallback, signAndSend } from "lib/util/tx";
import { NextPage } from "next";
import { ChangeEvent, useState } from "react";
import airdrop from "../public/airdrop.json";
import { environment } from "lib/constants";
import NotFoundPage from "./404";

const TOTAL_AIRDROP_ZTG = 1_000_000;
const ZTG_PER_ADDRESS = TOTAL_AIRDROP_ZTG / airdrop.length;
const AIRDROP_REMARK_PREFIX = "zeitgeist.airdrop-1";

const ClaimPage: NextPage = () => {
if (process.env.NEXT_PUBLIC_SHOW_AIRDROP !== "true") {
return <NotFoundPage />;
}
const [showEligibility, setShowEligibility] = useState(false);
const [polkadotAddress, setPolkadotAddress] = useState("");

const isValidPolkadotAddress =
polkadotAddress == "" || validateAddress(polkadotAddress, 0);

return (
<div className="relative mt-10 flex items-center justify-center">
<div
className="absolute z-[-1] h-full w-full overflow-hidden"
style={{
background:
"radial-gradient(50% 50% at 50% 50%, rgba(254, 207, 255, 0.3) 20.83%, rgba(205, 222, 255, 0.3) 54.17%, rgba(201, 232, 255, 0.3) 57.29%, rgba(245, 245, 245, 0) 100%)",
}}
></div>
<div className="flex max-w-[850px] flex-col items-center justify-center gap-y-5">
<div className="flex w-full gap-x-10">
<div className="w-full text-4xl font-bold sm:text-5xl sm:!leading-[77px] md:text-6xl">
Find out if you are eligible for the Airdrop
</div>
<img
className="relative mr-auto hidden w-2/5 scale-110 sm:block"
src="/airdrop.svg"
alt="Airdrop"
/>
</div>
<div className="w-full whitespace-pre-wrap text-lg">
This airdrop is designed for those who have actively participated in
Polkadot's OpenGov by voting before the start of{" "}
<a href="https://polkadot.polkassembly.io/referenda/502">
Referendum 502
</a>
. The snapshot was taken February 14th, 2024 (22:14:54 UTC). Only
wallets that voted on Polkadot's OpenGov before the snapshot will be
eligible. Claims will be open until July 1st, 2024.
</div>
{showEligibility === false ? (
<>
<div className="w-full text-xl font-bold">
Enter your Polkadot address below to check your eligibility:
</div>
<div className="flex w-full flex-col gap-4 rounded-md bg-[#DFE5ED] p-7 sm:flex-row">
<div className="relative flex w-full flex-col">
<input
className="w-full rounded-md bg-white p-2"
placeholder="Enter Polkadot address"
spellCheck={false}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
setPolkadotAddress(event.target.value);
}}
/>
{isValidPolkadotAddress === false && (
<div className="absolute top-10 text-xs text-red-600">
Invalid Polkadot address
</div>
)}
</div>

<button
className="h-[40px] w-full rounded-md bg-[#2468E2] text-white disabled:opacity-50 sm:w-[200px]"
onClick={() => {
setShowEligibility(true);
}}
disabled={
polkadotAddress === "" ||
polkadotAddress == null ||
isValidPolkadotAddress === false
}
>
Check Eligibility
</button>
</div>
</>
) : (
<Eligibility
polkadotAddress={polkadotAddress}
onCheckAgain={() => {
setShowEligibility(false);
setPolkadotAddress("");
}}
/>
)}
</div>
</div>
);
};

const Eligibility = ({
polkadotAddress,
onCheckAgain,
}: {
polkadotAddress: string;
onCheckAgain: () => void;
}) => {
const wallet = useWallet();
const notifications = useNotifications();

const [claimAddress, setClaimAddress] = useState<string | null>(null);
const { api } = usePolkadotApi();

const isEligible = airdrop.some(
(airdropAddress) => airdropAddress === polkadotAddress,
);

const isValid = claimAddress === null || validateAddress(claimAddress, 73);
const tx = api?.tx.system.remark(`${AIRDROP_REMARK_PREFIX}-${claimAddress}`);

const connectedWalletMatchesPolkadotAddress = addressesMatch(
polkadotAddress,
wallet.realAddress ?? "",
);

const txHex = tx?.toHex();

const submitClaim = () => {
if (!tx || !api) return;

const signer = wallet.getSigner();

if (!signer) return;

signAndSend(
tx,
signer,
extrinsicCallback({
api: api,
notifications,
broadcastCallback: () => {
notifications?.pushNotification("Broadcasting transaction...", {
autoRemove: true,
});
},
successCallback: (data) => {
notifications?.pushNotification(`Successfully claimed`, {
autoRemove: true,
type: "Success",
});
},
failCallback: (error) => {
notifications.pushNotification(error, { type: "Error" });
},
}),
).catch((error) => {
notifications.pushNotification(error?.toString() ?? "Unknown Error", {
type: "Error",
});
});
};

return (
<>
{isEligible ? (
<>
{connectedWalletMatchesPolkadotAddress ? (
<div className="w-full text-xl font-bold">
You are eligible for at least {Math.floor(ZTG_PER_ADDRESS)} ZTG,
enter Zeitgeist address to claim
</div>
) : (
<div className="w-full text-xl font-bold">
This address is eligible for at least{" "}
{Math.floor(ZTG_PER_ADDRESS)} ZTG, enter Zeitgeist address to
claim
</div>
)}
<div className="flex w-full flex-col">
<div className="flex w-full flex-col gap-4 rounded-md bg-[#DFE5ED] p-7 sm:flex-row">
<div className="relative flex w-full flex-col">
<input
className="w-full rounded-md bg-white p-2"
placeholder="Zeitgeist Address"
spellCheck={false}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
setClaimAddress(event.target.value);
}}
/>
{isValid === false && (
<div className="absolute top-10 text-xs text-red-600">
Invalid Zeitgeist address
</div>
)}
{isValid === true &&
claimAddress != null &&
connectedWalletMatchesPolkadotAddress === false && (
<div className="absolute top-10 text-xs text-red-600">
Connected wallet doesn't match Polkadot address
</div>
)}
</div>
<button
className="h-[40px] w-full rounded-md bg-[#2468E2] text-white disabled:opacity-50 sm:w-[200px]"
disabled={
claimAddress === null ||
isValid === false ||
wallet.connected === false ||
connectedWalletMatchesPolkadotAddress === false
}
onClick={() => submitClaim()}
>
Claim Airdrop
</button>
</div>
<a
href={`https://polkadot.js.org/apps/?rpc=wss%3A%2F%2F${
environment === "production"
? "rpc.polkadot.io"
: "rococo-rpc.polkadot.io"
}#/extrinsics/decode/${txHex}`}
target="_blank"
rel="noreferrer"
className="mt-3 text-sm text-blue-700"
>
Wallet not supported? Enter Zeitgeist address and sign with
Polkadot.js/apps
</a>
</div>
</>
) : (
<div className="w-full text-xl font-bold">
You are not eligible for this airdrop
</div>
)}
<div className="flex w-full">
<button
className="h-[40px] w-[200px] rounded-md bg-[#2468E2] text-white"
onClick={() => onCheckAgain()}
>
Check another address
</button>
</div>
</>
);
};

const validateAddress = (address: string, ss58Format: number) => {
try {
const encodedAddress = encodeAddress(
decodeAddress(address),
ss58Format,
).toString();

return encodedAddress === address;
} catch {
return false;
}
};

const addressesMatch = (address1: string, address2: string) => {
try {
const encodedAddress1 = encodeAddress(
decodeAddress(address1),
0,
).toString();
const encodedAddress2 = encodeAddress(
decodeAddress(address2),
0,
).toString();

return encodedAddress1 === encodedAddress2;
} catch {
return false;
}
};

export default ClaimPage;
Loading

0 comments on commit de34e98

Please sign in to comment.