Skip to content

Commit

Permalink
Merge pull request #696 from subspace/main
Browse files Browse the repository at this point in the history
Sync prod with main
  • Loading branch information
marc-aurele-besner committed Jul 9, 2024
2 parents 49ab5a5 + 855ac77 commit 1670400
Show file tree
Hide file tree
Showing 29 changed files with 905 additions and 94 deletions.
7 changes: 7 additions & 0 deletions explorer/.env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,15 @@ DISCORD_GUILD_ROLE_ID_FARMER="<discord-farmer-role-id>"
NEXT_PUBLIC_RPC_URL="wss://"
NEXT_PUBLIC_NOVA_RPC_URL="wss://"

SLACK_TOKEN=""
SLACK_CONVERSATION_ID=""

NEXT_PUBLIC_SHOW_LOCALHOST="false"

WALLET_CLAIM_OPERATOR_DISBURSEMENT_URI="//Alice"
CLAIM_OPERATOR_DISBURSEMENT_AMOUNT="100000000000000000000"
CLAIM_WALLET_LOW_FUND_WARNING="500000000000000000000"

# Mock Wallet Configuration (to be used for development only)
# NEXT_PUBLIC_MOCK_WALLET="true"
# NEXT_PUBLIC_MOCK_WALLET_ADDRESS="" # jeremy
Expand Down
3 changes: 3 additions & 0 deletions explorer/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ const nextConfig = {
exclude: /node_modules/,
loader: 'graphql-tag/loader',
})
if (!isServer) {
config.resolve.fallback.fs = false
}
return config
},
}
Expand Down
2 changes: 2 additions & 0 deletions explorer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
"dependencies": {
"@apollo/client": "^3.7.0",
"@apollo/experimental-nextjs-app-support": "^0.8.0",
"@autonomys/auto-consensus": "^0.1.4",
"@autonomys/auto-utils": "^0.1.4",
"@headlessui/react": "^1.7.18",
"@headlessui/tailwindcss": "^0.2.0",
"@heroicons/react": "^2.1.1",
Expand Down
136 changes: 136 additions & 0 deletions explorer/src/app/api/claim/[...params]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { AuthProvider } from '@/constants'
import { balance, transfer } from '@autonomys/auto-consensus'
import { ActivateWalletInput, activateWallet } from '@autonomys/auto-utils'
import type { ApiPromise } from '@polkadot/api'
import { stringToU8a } from '@polkadot/util'
import { decodeAddress, signatureVerify } from '@polkadot/util-crypto'
import { chains } from 'constants/chains'
import { CLAIM_TYPES } from 'constants/routes'
import { NextRequest, NextResponse } from 'next/server'
import { verifyToken } from 'utils/auth/verifyToken'
import {
findClaim,
findClaimStats,
findUserByID,
saveClaim,
saveClaimStats,
updateClaimStats,
updateUser,
} from 'utils/fauna'
import { formatUnitsToNumber } from 'utils/number'
import { sendSlackStatsMessage, walletBalanceLowSlackMessage } from 'utils/slack'

export const POST = async (req: NextRequest) => {
try {
if (!process.env.WALLET_CLAIM_OPERATOR_DISBURSEMENT_URI)
throw new Error('Missing WALLET_CLAIM_OPERATOR_DISBURSEMENT_URI')
if (
!process.env.CLAIM_OPERATOR_DISBURSEMENT_AMOUNT &&
process.env.CLAIM_OPERATOR_DISBURSEMENT_AMOUNT !== '0'
)
throw new Error('Missing CLAIM_OPERATOR_DISBURSEMENT_AMOUNT')

const session = verifyToken()
console.log('session', session)

const dbSession = await findUserByID(session.id)
console.log('dbSession', dbSession)

if (!dbSession) return NextResponse.json({ error: 'User not found' }, { status: 404 })

const pathname = req.nextUrl.pathname
const chain = pathname.split('/').slice(3)[0]
const claimType = pathname.split('/').slice(4)[0]
if (claimType !== CLAIM_TYPES.OperatorDisbursement)
return NextResponse.json({ error: 'Invalid claim type' }, { status: 400 })

const chainMatch = chains.find((c) => c.urls.page === chain)
if (!chainMatch) return NextResponse.json({ error: 'Invalid chain' }, { status: 400 })

const previousClaim = await findClaim(session.id, chainMatch.urls.page, claimType)
if (previousClaim) return NextResponse.json({ error: 'Already claimed' }, { status: 400 })

const claim = await req.json()
const { message, signature, address } = claim

// Verify the signature
const publicKey = decodeAddress(address)
const isValid = signatureVerify(stringToU8a(message), signature, publicKey).isValid
if (!isValid) return NextResponse.json({ error: 'Invalid signature' }, { status: 400 })

const claimStats = await findClaimStats(chainMatch.urls.page, claimType)

const {
api,
accounts: [wallet],
} = await activateWallet({
uri: process.env.WALLET_CLAIM_OPERATOR_DISBURSEMENT_URI,
networkId: 'autonomys-' + chainMatch.urls.page,
} as ActivateWalletInput)

// Get wallet free balance
const { free } = await balance(api as unknown as ApiPromise, wallet.address)
if (BigInt(free) < BigInt(process.env.CLAIM_WALLET_LOW_FUND_WARNING || 1000 * 10 ** 18))
await walletBalanceLowSlackMessage(
formatUnitsToNumber(free.toString()).toString(),
wallet.address,
)

if (BigInt(free) <= BigInt(process.env.CLAIM_OPERATOR_DISBURSEMENT_AMOUNT))
return NextResponse.json({ error: 'Insufficient funds' }, { status: 400 })

// Create and sign the transfer transaction
const block = await api.rpc.chain.getBlock()
const tx = await transfer(
api as unknown as ApiPromise,
address,
process.env.CLAIM_OPERATOR_DISBURSEMENT_AMOUNT,
)
const hash = await tx.signAndSend(wallet)
const txReceipt = {
ownerAccount: wallet.address,
status: 'pending',
submittedAtBlockHash: block.block.header.hash.toHex(),
submittedAtBlockNumber: block.block.header.number.toNumber(),
call: 'balances.transferKeepAlive',
txHash: hash.hash.toHex(),
blockHash: '',
}

await saveClaim(session, chainMatch.urls.page, claimType, claim, txReceipt)

if (!claimStats) {
const slackMessage = await sendSlackStatsMessage(1)
if (slackMessage) await saveClaimStats(session, chainMatch.urls.page, claimType, slackMessage)
} else {
await sendSlackStatsMessage(
claimStats[0].data.totalClaims + 1,
claimStats[0].data.slackMessageId,
)
await updateClaimStats(claimStats[0].ref, claimStats[0].data, session)
}

await api.disconnect()

await updateUser(dbSession[0].ref, dbSession[0].data, AuthProvider.subspace, {
...dbSession[0].data.subspace,
disbursements:
dbSession[0].data.subspace && dbSession[0].data.subspace.disbursements
? {
...dbSession[0].data.subspace.disbursements,
stakeWars2: true,
}
: {
stakeWars2: true,
},
})

return NextResponse.json({
message: 'Disbursement claimed successfully',
hash: hash.hash.toHex(),
})
} catch (error) {
console.error('Error processing disbursement:', error)
return NextResponse.json({ error: 'Failed to claim disbursement' }, { status: 500 })
}
}
4 changes: 2 additions & 2 deletions explorer/src/components/Account/AccountDetailsCard.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { shortString } from '@/utils/string'
import Identicon from '@polkadot/react-identicon'
import { Accordion } from 'components/common/Accordion'
import { CopyButton } from 'components/common/CopyButton'
import { List, StyledListItem } from 'components/common/List'
import { Account } from 'gql/graphql'
import useDomains from 'hooks/useDomains'
import { FC } from 'react'
import { accountIdToHex } from 'utils//formatAddress'
import { AccountIcon } from '../common/AccountIcon'

type Props = {
account: Account | undefined
Expand All @@ -25,7 +25,7 @@ export const AccountDetailsCard: FC<Props> = ({ account, accountAddress, isDeskt
<Accordion
title={
<div className='flex w-full items-center gap-3'>
<Identicon value={accountAddress} size={48} theme={theme} />
<AccountIcon address={accountAddress} theme={theme} />

<h3 className='break-all text-sm font-medium leading-none text-grayDark dark:text-white'>
{accountAddress}
Expand Down
4 changes: 2 additions & 2 deletions explorer/src/components/Account/AccountList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { PAGE_SIZE, searchTypes } from '@/constants'
import { bigNumberToNumber, numberWithCommas } from '@/utils/number'
import { shortString } from '@/utils/string'
import { useApolloClient, useQuery } from '@apollo/client'
import Identicon from '@polkadot/react-identicon'
import type { SortingState } from '@tanstack/react-table'
import { useEvmExplorerBanner } from 'components/common/EvmExplorerBanner'
import { SearchBar } from 'components/common/SearchBar'
Expand All @@ -20,6 +19,7 @@ import { FC, useCallback, useMemo, useState } from 'react'
import { useErrorHandler } from 'react-error-boundary'
import type { Cell } from 'types/table'
import { downloadFullData } from 'utils/downloadFullData'
import { AccountIcon } from '../common/AccountIcon'
import { QUERY_ACCOUNT_CONNECTION_LIST } from './query'

export const AccountList: FC = () => {
Expand Down Expand Up @@ -84,7 +84,7 @@ export const AccountList: FC = () => {
enableSorting: false,
cell: ({ row }: Cell<Account>) => (
<div key={`${row.index}-account-id`} className='row flex items-center gap-3'>
<Identicon value={row.original.id} size={26} theme={theme} />
<AccountIcon address={row.original.id} size={26} theme={theme} />
<Link
data-testid={`account-link-${row.index}`}
href={INTERNAL_ROUTES.accounts.id.page(
Expand Down
4 changes: 2 additions & 2 deletions explorer/src/components/Leaderboard/NominatorRewardsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { PAGE_SIZE } from '@/constants/general'
import { bigNumberToString } from '@/utils/number'
import { shortString } from '@/utils/string'
import { useApolloClient, useQuery } from '@apollo/client'
import Identicon from '@polkadot/react-identicon'
import { SortingState } from '@tanstack/react-table'
import { DebouncedInput } from 'components/common/DebouncedInput'
import { NotAllowed } from 'components/common/NotAllowed'
Expand All @@ -20,6 +19,7 @@ import { useCallback, useMemo, useState } from 'react'
import { useErrorHandler } from 'react-error-boundary'
import { downloadFullData } from 'utils/downloadFullData'
import { sort } from 'utils/sort'
import { AccountIcon } from '../common/AccountIcon'
import { NotFound } from '../layout/NotFound'
import { QUERY_NOMINATORS_REWARDS_LIST } from './querys'

Expand Down Expand Up @@ -60,7 +60,7 @@ export const NominatorRewardsList = () => {
cell: ({ row }: Row) => {
return (
<div className='row flex items-center gap-3'>
<Identicon value={row.original.id} size={26} theme='beachball' />
<AccountIcon address={row.original.id} size={26} />
<Link
data-testid={`account-link-${row.index}`}
href={INTERNAL_ROUTES.accounts.id.page(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { bigNumberToNumber, numberWithCommas } from '@/utils/number'
import Identicon from '@polkadot/react-identicon'
import { MobileCard } from 'components/common/MobileCard'
import { INTERNAL_ROUTES } from 'constants/routes'
import { AccountRewards } from 'gql/graphql'
import useDomains from 'hooks/useDomains'
import Link from 'next/link'
import { FC } from 'react'
import { AccountIcon } from '../common/AccountIcon'

type Props = {
account: AccountRewards
Expand All @@ -28,15 +28,15 @@ export const NominatorRewardsListCard: FC<Props> = ({ account, index }) => {
id='account-list-mobile'
header={
<div key={`${account.id}-account-id`} className='row -mx-1 -mt-3 flex items-center gap-3'>
<Identicon value={account.id} size={49} theme='beachball' />
<AccountIcon address={account.id} />
<Link
href={INTERNAL_ROUTES.accounts.id.page(
selectedChain.urls.page,
'consensus',
account.id,
)}
>
<p className='text-grayDarker break-all text-sm font-medium dark:text-white'>
<p className='break-all text-sm font-medium text-grayDarker dark:text-white'>
{account.id}
</p>
</Link>
Expand Down
4 changes: 2 additions & 2 deletions explorer/src/components/Leaderboard/VoteBlockRewardList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { PAGE_SIZE } from '@/constants/general'
import { bigNumberToNumber, numberWithCommas } from '@/utils/number'
import { shortString } from '@/utils/string'
import { useApolloClient, useQuery } from '@apollo/client'
import Identicon from '@polkadot/react-identicon'
import { SortingState } from '@tanstack/react-table'
import { DebouncedInput } from 'components/common/DebouncedInput'
import { NotAllowed } from 'components/common/NotAllowed'
Expand All @@ -21,6 +20,7 @@ import { useErrorHandler } from 'react-error-boundary'
import type { Cell } from 'types/table'
import { downloadFullData } from 'utils/downloadFullData'
import { sort } from 'utils/sort'
import { AccountIcon } from '../common/AccountIcon'
import { NotFound } from '../layout/NotFound'
import { QUERY_REWARDS_LIST } from './querys'

Expand Down Expand Up @@ -62,7 +62,7 @@ export const VoteBlockRewardList = () => {
>) => {
return (
<div className='row flex items-center gap-3'>
<Identicon value={row.original.id} size={26} theme='beachball' />
<AccountIcon address={row.original.id} size={26} />
<Link
data-testid={`account-link-${row.index}`}
href={INTERNAL_ROUTES.accounts.id.page(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { bigNumberToNumber, numberWithCommas } from '@/utils/number'
import Identicon from '@polkadot/react-identicon'
import { MobileCard } from 'components/common/MobileCard'
import { INTERNAL_ROUTES } from 'constants/routes'
import type { AccountsConnectionRewardsQuery } from 'gql/graphql'
import useDomains from 'hooks/useDomains'
import Link from 'next/link'
import { FC } from 'react'
import { AccountIcon } from '../common/AccountIcon'

type Props = {
account: AccountsConnectionRewardsQuery['accountRewardsConnection']['edges'][0]['node']
Expand Down Expand Up @@ -40,15 +40,15 @@ export const VoteBlockRewardListCard: FC<Props> = ({ account, index }) => {
id='account-list-mobile'
header={
<div key={`${account.id}-account-id`} className='row -mx-1 -mt-3 flex items-center gap-3'>
<Identicon value={account.id} size={49} theme='beachball' />
<AccountIcon address={account.id} />
<Link
href={INTERNAL_ROUTES.accounts.id.page(
selectedChain.urls.page,
'consensus',
account.id,
)}
>
<p className='text-grayDarker break-all text-sm font-medium dark:text-white'>
<p className='break-all text-sm font-medium text-grayDarker dark:text-white'>
{account.id}
</p>
</Link>
Expand Down
Loading

0 comments on commit 1670400

Please sign in to comment.