diff --git a/README.md b/README.md index 7f97f7f6..ed17ca17 100644 --- a/README.md +++ b/README.md @@ -74,17 +74,6 @@ are generated in `gatsby-node.ts`. The pages text content is stored in markdown --- -## Crowdloan Pages - -Both crowdloan pages - -- `/parachain/crowdloan` -- `/altair/crowdloan` - -are generated in `gatsby-node.ts`. Most of their content is static because the auctions are closed already. The routes are taken over from the former site to keep urls alive. - ---- - ## GraphQL Fragments Queries of component data are defined inside the component file. To be able to spread them into the page query, they are exported as fragments. diff --git a/data/crowdloan/altair.json b/data/crowdloan/altair.json deleted file mode 100644 index 1765d407..00000000 --- a/data/crowdloan/altair.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "slug": "/altair", - "seo": { - "title": "Altair Wins 9th Slot in Kusama Auctions | Centrifuge" - }, - "network": "altair", - "hero_crowdloan": { - "title": "Altair Wins 9th Slot in Kusama Auctions", - "amount": "187.84K KSM", - "contributors": "18,342" - }, - "stats": [ - { - "title": "Top referrers", - "items": [ - { - "address": "Fc2rXRok…Gu", - "amount": "641" - }, - { - "address": "CxZXGR8q…2v", - "amount": "570" - }, - { - "address": "EoqDfBE7…tF", - "amount": "367" - }, - { - "address": "DVSTJwhk…gu", - "amount": "354" - }, - { - "address": "G4SrBW47…ac", - "amount": "153" - } - ] - }, - { - "title": "Top contributors", - "items": [ - { - "address": "HNf7Bz3Q…ic", - "amount": "25,000 KSM" - }, - { - "address": "DDTVgKoS…c1", - "amount": "11,000 KSM" - }, - { - "address": "FFis2qYe…yr", - "amount": "7,000 KSM" - }, - { - "address": "J6bvL3mh…ZW", - "amount": "5,000 KSM" - }, - { - "address": "EkTrycMC…Ck", - "amount": "3,000 KSM" - } - ] - } - ] -} diff --git a/data/crowdloan/parachain.json b/data/crowdloan/parachain.json deleted file mode 100644 index 48dc3b22..00000000 --- a/data/crowdloan/parachain.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "slug": "/parachain", - "seo": { - "title": "Centrifuge Parachain Crowdloan | Centrifuge" - }, - "network": "centrifuge", - "hero_crowdloan": { - "title": "Boom! Centrifuge wins slot in Parachain auctions", - "body": "5,435,161 DOT raised from 27,101 contributions", - "amount": "5,435,161 DOT", - "contributors": "27,101", - "anchor": { - "label": "Learn more on parachains.info", - "href": "https://parachains.info/" - } - }, - "stats": [ - { - "title": "Top referrers", - "items": [ - { - "address": "14b8az…nLA9MJ", - "amount": "4485" - }, - { - "address": "12xiw7…5grrgU", - "amount": "3753" - }, - { - "address": "13Mf6q…QLJrm1", - "amount": "671" - }, - { - "address": "16DgBy…BZijXd", - "amount": "597" - }, - { - "address": "1PF1HL…MB5Mt1", - "amount": "565" - } - ] - }, - { - "title": "Top contributors", - "items": [ - { - "address": "12KjzR…JX3pMF", - "amount": "1,008,505 DOT" - }, - { - "address": "17QnZ6…cheWxx", - "amount": "549,970 DOT" - }, - { - "address": "12KweL…2a991h", - "amount": "350,000 DOT" - }, - { - "address": "162nEB…eao8cG", - "amount": "171,240 DOT" - }, - { - "address": "13gQM3…WgMFrH", - "amount": "100,007 DOT" - } - ] - } - ] -} diff --git a/functions/esbuild.js b/functions/esbuild.js index 15d4f085..425ee051 100644 --- a/functions/esbuild.js +++ b/functions/esbuild.js @@ -13,8 +13,6 @@ esbuild 'functions/index.ts', 'functions/src/getExample.ts', 'functions/src/getLeverPositions.ts', - 'functions/src/createProof.ts', - 'functions/src/getRewardData.ts', 'functions/src/getPoolsData.ts', 'functions/src/getTotalAssetsTokenized.ts', ], diff --git a/functions/src/createProof.ts b/functions/src/createProof.ts deleted file mode 100644 index bc1e200f..00000000 --- a/functions/src/createProof.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Request, Response } from 'express' -import { createProof as createActualProof, getContributionAmount } from './handleCreateProof' -const { u8aToHex } = require('@polkadot/util') -const { decodeAddress } = require('@polkadot/util-crypto') -const JSONbig = require('json-bigint')({ - useNativeBigInt: true, - alwaysParseAsBig: true, -}) - -export default async function createProof(req: Request, res: Response) { - if (req.method !== 'POST') { - return res.status(405).send('Method not allowed. Use POST.') - } - - try { - const { address, parachain } = JSON.parse(req.body) - - const merkleTree = - parachain === 'centrifuge' - ? require('../../config/crowdloan/centrifuge-reward-merkle-tree.js').merkleTree - : require('../../config/crowdloan/altair-reward-merkle-tree.js').merkleTree - - const hexAddress = u8aToHex(decodeAddress(address)) - - const proof = createActualProof(hexAddress, merkleTree) - const contribution = getContributionAmount(hexAddress, merkleTree) - - return res.status(200).send( - JSONbig.stringify({ - proof, - signMessage: hexAddress, - contribution, - }) - ) - } catch (error) { - return res.status(500).send(JSON.stringify(error)) - } -} diff --git a/functions/src/getRewardData.ts b/functions/src/getRewardData.ts deleted file mode 100644 index 8b144173..00000000 --- a/functions/src/getRewardData.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { u8aToHex } from '@polkadot/util' -import { decodeAddress } from '@polkadot/util-crypto' -import { Request, Response } from 'express' -import { getContributionAmount } from './handleCreateProof' - -export default async function getRewardData(req: Request, res: Response) { - if (req.method !== 'POST') { - return res.status(405).send('Method not allowed. Use POST.') - } - - const { address, parachain } = JSON.parse(req.body) - - const merkleTree = - parachain === 'centrifuge' - ? require('../../config/crowdloan/centrifuge-reward-merkle-tree.js').merkleTree - : require('../../config/crowdloan/altair-reward-merkle-tree.js').merkleTree - - const hexAddress = u8aToHex(decodeAddress(address)) - - const amount = getContributionAmount(hexAddress, merkleTree) - - return res.status(200).send( - JSON.stringify({ - address, - contributionAmount: String(amount), - }) - ) -} diff --git a/gatsby-node.ts b/gatsby-node.ts index b5e4e40f..8c0d700e 100644 --- a/gatsby-node.ts +++ b/gatsby-node.ts @@ -1,7 +1,7 @@ import type { GatsbyNode } from 'gatsby' import path from 'path' -export const createPages: GatsbyNode['createPages'] = async ({ actions: { createPage } }) => { +export const createPages: GatsbyNode['createPages'] = async ({ actions: { createPage, createRedirect } }) => { try { const legalPages = ['/imprint', '/terms', '/security', '/data-privacy-policy'] @@ -15,16 +15,9 @@ export const createPages: GatsbyNode['createPages'] = async ({ actions: { create }) }) - const crowdloanPages = ['/altair', '/parachain'] - - crowdloanPages.forEach((route) => { - createPage({ - path: `${route}/crowdloan`, - component: path.resolve('./src/templates/crowdloan.tsx'), - context: { - slug: route, - }, - }) + createRedirect({ + fromPath: `/parachain/crowdloan`, + toPath: `/`, }) } catch (err) { console.log('error', err) diff --git a/src/components/StatsCrowdloan.tsx b/src/components/StatsCrowdloan.tsx deleted file mode 100644 index effdf221..00000000 --- a/src/components/StatsCrowdloan.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { Grid, Shelf, Box, Container, Text, Stack } from '@centrifuge/fabric' -import React from 'react' -import { graphql } from 'gatsby' - -export const query = graphql` - fragment StatsCrowdloanFragment on CrowdloanJsonStats { - title - items { - address - amount - } - } -` - -export type StatsCrowdloanProps = { - stats: { - title: string - items: { - address: string - amount: string - }[] - }[] -} - -export function StatsCrowdloan({ stats }: StatsCrowdloanProps) { - return ( - - - {stats.map((entry) => ( - - - {entry.title} - - - - {entry.items.map(({ address, amount }, index) => ( - - - {address} - - - {amount} - - - ))} - - - ))} - - - ) -} diff --git a/src/components/crowdloan-user/User.tsx b/src/components/crowdloan-user/User.tsx deleted file mode 100644 index a5a702fc..00000000 --- a/src/components/crowdloan-user/User.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import { - formatBalanceAbbreviated, - useCentrifuge, - useCentrifugeTransaction, - useWallet, - WalletMenu, -} from '@centrifuge/centrifuge-react' -import { Button, Container, Shelf, Stack, Text } from '@centrifuge/fabric' -import { decodeAddress, signatureVerify } from '@polkadot/util-crypto' -import type { WalletAccount } from '@subwallet/wallet-connect/types' -import BN from 'bn.js' -import * as React from 'react' -import { switchMap } from 'rxjs' -import { getAccountDetails, useDidClaim, useTotalRewards } from './utils' - -export function User() { - const [isClaiming, setIsClaiming] = React.useState(false) - const centrifuge = useCentrifuge() - const { - substrate: { selectedAccount }, - } = useWallet() - const didClaim = useDidClaim(selectedAccount?.address) - const totalRewards = useTotalRewards({ address: selectedAccount?.address, parachain: centrifuge.config.network }) - const currency = centrifuge.config.network === 'altair' ? 'AIR' : 'CFG' - - const { execute, isLoading } = useCentrifugeTransaction( - 'Claiming rewards', - (cent) => - ([proof, signature, address]: [any, string, string], options) => { - return cent.getApi().pipe( - switchMap((api) => { - const verification = signatureVerify(proof.signMessage, signature, decodeAddress(address)) - if (!['sr25519', 'ed25519', 'ecdsa'].includes(verification.crypto)) { - throw new Error('Verification of signature failed with given account.') - } - - const submittable = api.tx.crowdloanClaim.claimReward( - address, - address, - { - [verification.crypto]: signature, - }, - { - leafHash: proof.proof.leafHash, - sortedHashes: proof.proof.sortedHashes, - }, - proof.contribution - ) - - return cent.wrapSignAndSend(api, submittable, options) - }) - ) - } - ) - - async function claim(account: WalletAccount | null) { - if (!account) { - return - } - - setIsClaiming(true) - - await getAccountDetails(account, centrifuge.config.network) - .then((response) => { - if (response) { - execute(response) - } - }) - .catch((error) => console.log(error)) - .finally(() => { - setIsClaiming(false) - }) - } - - return ( - - - - - {didClaim != null && - (didClaim ? ( - - - Rewards already claimed - - - ) : ( - - - Total rewards:{' '} - {totalRewards ? ( - - {formatBalanceAbbreviated(totalRewards, currency)} - - ) : ( - - 0.0 - - )} - - {!!totalRewards && totalRewards?.gt(new BN(0)) && ( - - )} - - ))} - - - Learn how to claim - - - - ) -} diff --git a/src/components/crowdloan-user/index.tsx b/src/components/crowdloan-user/index.tsx deleted file mode 100644 index 764fd15c..00000000 --- a/src/components/crowdloan-user/index.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { UserProvidedConfig } from '@centrifuge/centrifuge-js' -import { Provider } from '@centrifuge/centrifuge-react' -import * as React from 'react' -import { User } from './User' - -export type CrowdloanUserProps = { - network: 'altair' | 'centrifuge' -} - -export default function CrowdloanUser({ network }: CrowdloanUserProps) { - const centConfig: UserProvidedConfig = React.useMemo( - () => ({ - network, - ...(process.env.NODE_ENV === 'development' && { - centrifugeWsUrl: 'wss://fullnode.development.cntrfg.com', - altairWsUrl: 'wss://fullnode.development.cntrfg.com', - }), - }), - [] - ) - - return ( - - - - ) -} diff --git a/src/components/crowdloan-user/utils.tsx b/src/components/crowdloan-user/utils.tsx deleted file mode 100644 index 59afca06..00000000 --- a/src/components/crowdloan-user/utils.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { CurrencyBalance } from '@centrifuge/centrifuge-js' -import { useCentrifugeQuery } from '@centrifuge/centrifuge-react' -import { cryptoWaitReady } from '@polkadot/util-crypto' -import type { WalletAccount } from '@subwallet/wallet-connect/types' -import jsonBigInt from 'json-bigint' -import { useQuery } from 'react-query' -import { map, switchMap } from 'rxjs' -import type { CrowdloanUserProps } from './index' - -const JsonBig = jsonBigInt({ useNativeBigInt: true, alwaysParseAsBig: true }) - -export function useTotalRewards({ - address, - parachain, -}: { - address?: WalletAccount['address'] - parachain: CrowdloanUserProps['network'] -}) { - const { data } = useQuery( - ['getRewardData', address, parachain], - async () => { - const response = await fetch(`${process.env.GATSBY_FUNCTIONS_URL}/getRewardData`, { - method: 'POST', - body: JSON.stringify({ - address, - parachain, - }), - }) - - const json = await response.json() - const amount = new CurrencyBalance(json.contributionAmount, 18) - return amount - }, - { - enabled: !!address, - } - ) - - return data -} - -export function useDidClaim(address?: string) { - const [data] = useCentrifugeQuery( - ['claimedRewards', address], - (cent) => { - return cent.getApi().pipe( - switchMap((api) => { - return api.query.crowdloanClaim.processedClaims([address, 1]) - }), - map((didClaim) => { - return didClaim.toHuman() ? true : false - }) - ) - }, - { - enabled: !!address, - } - ) - return data -} - -export async function getAccountDetails( - account: WalletAccount, - parachain: CrowdloanUserProps['network'] -): Promise<[any, string, string] | null> { - if (!account) { - return null - } - - const proof = await fetch(`${process.env.GATSBY_FUNCTIONS_URL}/createProof`, { - method: 'POST', - body: JSON.stringify({ address: account.address, parachain }), - }) - .then(async (response) => { - if (!response.ok) { - throw new Error('Could not create proof') - } - - return await response.text() - }) - .then((text) => JsonBig.parse(text)) - .catch((error) => console.log(error)) - - const signature = await cryptoWaitReady() - .then((_) => account.wallet?.signer?.signRaw) - .then(async (signRaw) => { - if (!signRaw) { - throw new Error('signRaw was not defined') - } - - return await signRaw({ - address: account.address, - data: proof.signMessage, - type: 'bytes', - }) - }) - .then((payload) => payload.signature) - .catch((error) => console.log(error)) - - return [proof, signature as string, account.address] -} diff --git a/src/components/hero-crowdloan/index.tsx b/src/components/hero-crowdloan/index.tsx deleted file mode 100644 index 1521415f..00000000 --- a/src/components/hero-crowdloan/index.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import React from 'react' -import { graphql } from 'gatsby' -import { Container, Box, Text, AnchorButton } from '@centrifuge/fabric' - -export const query = graphql` - fragment HeroCrowdloanFragment on CrowdloanJsonHero_crowdloan { - title - amount - contributors - anchor { - label - href - } - } -` - -export type HeroCrowdloanProps = { - title: string - amount: string - contributors: string - anchor?: { - label: string - href: string - } -} - -export function HeroCrowdloan({ title, amount, contributors, anchor }: HeroCrowdloanProps) { - return ( - - - Crowdloan - - - {title} - - - {amount} raised from {contributors} contributions - - {anchor && ( - - - {anchor.label} - - - )} - - ) -} - -function Em({ children }: { children: React.ReactNode }) { - return ( - - {children} - - ) -} diff --git a/src/templates/crowdloan.tsx b/src/templates/crowdloan.tsx deleted file mode 100644 index b3aab9fd..00000000 --- a/src/templates/crowdloan.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import * as React from 'react' -import { Box, Stack, Text } from '@centrifuge/fabric' -import type { HeadProps } from 'gatsby' -import { graphql } from 'gatsby' -import type { CrowdloanUserProps } from '../components/crowdloan-user' -import type { HeroCrowdloanProps } from '../components/hero-crowdloan' -import { HeroCrowdloan } from '../components/hero-crowdloan' -import { Layout } from '../components/Layout' -import type { SEOProps } from '../components/Seo' -import { SEO } from '../components/Seo' -import type { StatsCrowdloanProps } from '../components/StatsCrowdloan' -import { StatsCrowdloan } from '../components/StatsCrowdloan' - -const CrowdloanUser = React.lazy(() => import('../components/crowdloan-user')) - -export const query = graphql` - query ($slug: String!) { - crowdloanJson(slug: { eq: $slug }) { - seo { - title - } - - network - - hero_crowdloan { - ...HeroCrowdloanFragment - } - - stats { - ...StatsCrowdloanFragment - } - } - } -` - -type CrowdloanPageProps = { - data: { - crowdloanJson: { - seo: SEOProps - network: CrowdloanUserProps['network'] - hero_crowdloan: HeroCrowdloanProps - stats: StatsCrowdloanProps['stats'] - } - } -} - -export default function CrowdloanPage({ data }: CrowdloanPageProps) { - const [isRendered, setIsRendered] = React.useState(false) - const { hero_crowdloan, stats, network } = data.crowdloanJson - - React.useEffect(() => setIsRendered(true), []) - - return ( - - - - Auction ended — Closed for contribution - - - - - {isRendered ? : null} - - - - ) -} - -export const Head = ({ data, location }: CrowdloanPageProps & HeadProps) => { - const { seo } = data.crowdloanJson - const { pathname } = location - - return -}