diff --git a/.env.example b/.env.example index c5cde61..d50a27a 100644 --- a/.env.example +++ b/.env.example @@ -3,7 +3,7 @@ NEXT_PUBLIC_RPC_HOST=http://127.0.0.1:8545 COSMOS_RPC_HOST=http://127.0.0.1:1317 # Proxy -GITHUB_API_PROXY= +GH_API_PROXY= # Chain info NEXT_PUBLIC_CHAIN_ID=9000 diff --git a/.github/workflows/types-lint-license-tests.yml b/.github/workflows/types-lint-license-tests.yml new file mode 100644 index 0000000..3d908cf --- /dev/null +++ b/.github/workflows/types-lint-license-tests.yml @@ -0,0 +1,112 @@ +name: Check types, lint, license and run tests + +on: + pull_request: + branches: + - main + +jobs: + setup: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20.16.0' + + - name: Install pnpm + uses: pnpm/action-setup@v2 + with: + version: 8 + + - name: Install dependencies + run: pnpm install + + - name: Cache dependencies + uses: actions/cache@v3 + id: cache + with: + path: ~/.pnpm-store + key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm- + + test: + needs: setup + runs-on: ubuntu-latest + env: + NEXT_PUBLIC_RPC_HOST: 'http://127.0.0.1:8545' + NEXT_PUBLIC_ENABLED_LOGS: true + GH_API_PROXY: ${{ secrets.GH_API_PROXY }} + MOCK_COINGECKO_API: true + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20.16.0' + - uses: pnpm/action-setup@v2 + with: + version: 8 + - uses: actions/cache@v3 + with: + path: ~/.pnpm-store + key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} + - run: pnpm install + - run: pnpm test + + typecheck: + needs: setup + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20.16.0' + - uses: pnpm/action-setup@v2 + with: + version: 8 + - uses: actions/cache@v3 + with: + path: ~/.pnpm-store + key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} + - run: pnpm install + - run: pnpm typecheck + + lint: + needs: setup + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20.16.0' + - uses: pnpm/action-setup@v2 + with: + version: 8 + - uses: actions/cache@v3 + with: + path: ~/.pnpm-store + key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} + - run: pnpm install + - run: pnpm lint + + license: + needs: setup + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20.16.0' + - uses: pnpm/action-setup@v2 + with: + version: 8 + - uses: actions/cache@v3 + with: + path: ~/.pnpm-store + key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} + - run: pnpm install + - run: pnpm license diff --git a/README.md b/README.md index d1ffb15..ea34288 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -🚧 Work in progress, not ready for production 🚧 -

Evmos Burn Auction Instant dApp

@@ -32,6 +30,8 @@ Pre-requisites: ```bash pnpm install +pnpm prisma:db:push + cp .env.example .env # edit the .env file with your own values ``` @@ -55,7 +55,7 @@ pnpm build ### Test with a local node -Follow the instructions described in this issue in order to run a local node with the Burn Auction module enabled: https://github.com/evmos/burn-auction-dapp/issues/5 +Follow the instructions described in this issue in order to run a local node with the Burn Auction module enabled check this [file](./docs/DEVELOP_WITH_LOCAL_NODE.md). ### Indexing Endpoints diff --git a/package.json b/package.json index 2ac901b..3bbe9d0 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "prepare": "husky", "test": "vitest", "gen:types": "cross-env NODE_OPTIONS='--experimental-import-meta-resolve' tsx ./src/utilities/registry/scripts/gen-schema-types.mts", - "postinstall": "npm run gen:types", + "postinstall": "npm run gen:types && npm run prisma:generate", "license": "node ./check-license.mjs", "license:fix": "node ./check-license.mjs --fix", "typecheck": "tsc --noEmit", @@ -73,4 +73,3 @@ "vitest": "^2.0.5" } } - diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2357417..4cdd49c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1538,6 +1538,10 @@ packages: resolution: {integrity: sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==} dev: true + /@types/luxon@3.4.2: + resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==} + dev: false + /@types/mime@1.3.5: resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} dev: false diff --git a/src/app/_components/AuctionDetails.tsx b/src/app/_components/AuctionDetails.tsx index 25e559a..c1f5d44 100644 --- a/src/app/_components/AuctionDetails.tsx +++ b/src/app/_components/AuctionDetails.tsx @@ -9,7 +9,7 @@ import type { AuctionDetailed } from '@/types/AuctionDetailed'; import { formatUnits } from '@/utilities/formatUnits'; import { EVMOS_DECIMALS } from '@/constants'; import { ButtonLink } from '@/components/ui/ButtonLink'; -import { fetchCurrentCryptoPrice } from '@/queries/fetchCurrentCryptoPrice'; +import { Tooltip } from '@/components/ui/Tooltip'; import { AssetsTable } from './AssetsTable'; import { BiddingHistory } from './BiddingHistory'; @@ -18,12 +18,6 @@ import { Countdown } from './Countdown'; import { BiddingProgress } from './BiddingProgress'; import { DiscountChip } from './DiscountChip'; -import { EVMOS_DECIMALS } from '@/constants'; -import { ButtonLink } from '@/components/ui/ButtonLink'; -import Image from 'next/image'; -import { Tooltip } from '@/components/ui/Tooltip'; -import { fetchCurrentCryptoPrice } from '@/queries/fetchCurrentCryptoPrice'; - export const AuctionDetails = async ({ auctionDetails }: { auctionDetails: AuctionDetailed }) => { const { round, auction, highestBid }: AuctionDetailed = auctionDetails; @@ -49,8 +43,6 @@ export const AuctionDetails = async ({ auctionDetails }: { auctionDetails: Aucti timeZoneName: 'short', }); - const evmosToUsdRate = await fetchCurrentCryptoPrice(['evmos']).then((res) => res.evmos.usd); - return (
@@ -63,7 +55,7 @@ export const AuctionDetails = async ({ auctionDetails }: { auctionDetails: Aucti )} -

Auction #{Number(round.round)}

+

Auction #{Number(round.round)}

{round.isLast && ( @@ -119,12 +111,12 @@ export const AuctionDetails = async ({ auctionDetails }: { auctionDetails: Aucti

{highestBid.bidderAddress !== '0x0000000000000000000000000000000000000000' && ( - - {highestBid.bidderAddress} + + {highestBid.bidderAddress} Evmos Icon )}

-
{round.isLast && }
+
{round.isLast && }
diff --git a/src/app/_components/BiddingForm.tsx b/src/app/_components/BiddingForm.tsx index 55394f2..adb53e6 100644 --- a/src/app/_components/BiddingForm.tsx +++ b/src/app/_components/BiddingForm.tsx @@ -13,8 +13,9 @@ import { viemPublicClient } from '@/utilities/viem'; import { formatUnits } from '@/utilities/formatUnits'; import { EVMOS_DECIMALS } from '@/constants'; import { LoadingSpinner } from '@/components/ui/LoadingSpinner'; +import { Tooltip } from '@/components/ui/Tooltip'; -export const BiddingForm = ({ evmosToUsdRate }: { evmosToUsdRate: number }) => { +export const BiddingForm = ({ evmosToUsdRate, priceError }: { evmosToUsdRate: number; priceError: boolean }) => { const [state, send] = useMachine(biddingStateMachine); dappstore.onAccountsChange((accounts) => send({ type: 'SET_WALLET', wallet: accounts[0] })); @@ -39,7 +40,7 @@ export const BiddingForm = ({ evmosToUsdRate }: { evmosToUsdRate: number }) => { send({ type: 'SUBMIT' }); }; - const isSubmitDisabled = (state.context.bidAmount !== '' && !state.can({ type: 'SUBMIT' })) || state.matches('submitting'); + const isSubmitDisabled = (state.context.bidAmount !== '' && !state.can({ type: 'SUBMIT' })) || state.matches('submitting') || state.matches('success'); const errorMessage = Number(state.context.bidAmount) < 0 ? 'Bid amount must be greater than 0' @@ -66,7 +67,7 @@ export const BiddingForm = ({ evmosToUsdRate }: { evmosToUsdRate: number }) => { placeholder="Amount" value={state.context.bidAmount} onChange={(e) => send({ type: 'SET_BID_AMOUNT', value: e.target.value })} - disabled={state.matches('submitting')} + disabled={isSubmitDisabled} /> EVMOS @@ -74,16 +75,21 @@ export const BiddingForm = ({ evmosToUsdRate }: { evmosToUsdRate: number }) => { className="disabled:text-evmos-gray-light disabled:bg-evmos-gray disabled:border-evmos-gray items-center justify-center rounded-full transition-[background-color,outline-color,filter] transition-200 flex gap-x-1 outline outline-offset-2 outline-1 outline-transparent bg-evmos-orange-500 hover:bg-evmos-orange-400 py-[9px] px-5 active:outline-evmos-secondary-dark" disabled={isSubmitDisabled} > - {state.matches('submitting') ? : 'Bid'} + {state.matches('submitting') || state.matches('success') ? : 'Bid'} -
- {Number(state.context.bidAmount) > 0 && ≈ ${(Number(state.context.bidAmount) * evmosToUsdRate).toFixed(2)}} +
+ {Number(state.context.bidAmount) > 0 && ≈ ${(Number(state.context.bidAmount) * evmosToUsdRate).toFixed(2)}} + {Number(state.context.bidAmount) > 0 && priceError && ( + + Info + + )}
{errorMessage &&
{errorMessage}
} {state.matches('error') &&
{state.context.error}
} - {state.matches('success') &&
Bid placed successfully!
} + {state.matches('success') &&
Bid placed successfully! It will appear in a few seconds.
} ); }; diff --git a/src/app/_components/BiddingHistory.tsx b/src/app/_components/BiddingHistory.tsx index cc82ad4..a8f4888 100644 --- a/src/app/_components/BiddingHistory.tsx +++ b/src/app/_components/BiddingHistory.tsx @@ -40,20 +40,22 @@ export const BiddingHistory = async ({ round }: { round: bigint }) => { - - - - - - - - - - - - - + + + + + + + + + + + + + + + + } > diff --git a/src/app/_components/BiddingHistoryData.tsx b/src/app/_components/BiddingHistoryData.tsx index efe9b34..a874635 100644 --- a/src/app/_components/BiddingHistoryData.tsx +++ b/src/app/_components/BiddingHistoryData.tsx @@ -30,8 +30,8 @@ export const BiddingHistoryData = async ({ round }: { round: bigint }) => { {bids.map((bid) => ( - - {shortenAddress(bid.bidder)} + + {shortenAddress(bid.bidder)} Evmos Icon @@ -40,8 +40,8 @@ export const BiddingHistoryData = async ({ round }: { round: bigint }) => { {bid.time ? dayjs(bid.time).fromNow() : ''} - - {shortenAddress(bid.transactionHash)} + + {shortenAddress(bid.transactionHash)} Evmos Icon diff --git a/src/app/_components/Countdown.tsx b/src/app/_components/Countdown.tsx index 79adacc..510f4ae 100644 --- a/src/app/_components/Countdown.tsx +++ b/src/app/_components/Countdown.tsx @@ -3,9 +3,11 @@ 'use client'; import { useState, useEffect } from 'react'; -import reloadData from '../_actions/reloadData'; + import { Log } from '@/utilities/logger'; +import reloadData from '../_actions/reloadData'; + type TimeLeft = { days: number; hours: number; @@ -14,6 +16,7 @@ type TimeLeft = { }; const REFRESH_INTERVAL = 1000; +const DELAY_BEFORE_RELOAD_AFTER_COUNTDOWN_GOES_TO_ZERO = 5000; const calculateTimeLeft = (date: Date): TimeLeft => { const difference = +date - +new Date(); @@ -57,10 +60,10 @@ export const Countdown = ({ date }: { date: Date }) => { setTimeout(() => { Log().info('Reloading data after countdown to 0'); reloadData(); - }, 5000); + }, DELAY_BEFORE_RELOAD_AFTER_COUNTDOWN_GOES_TO_ZERO); } - }, 1000); - + }, REFRESH_INTERVAL); + return () => { clearInterval(interval); }; diff --git a/src/app/_state-machines/biddingStateMachine.ts b/src/app/_state-machines/biddingStateMachine.ts index 476a989..d2f2a34 100644 --- a/src/app/_state-machines/biddingStateMachine.ts +++ b/src/app/_state-machines/biddingStateMachine.ts @@ -42,6 +42,10 @@ export const biddingStateMachine = setup({ bidAmount: ({ context }) => Math.max(Number(formatUnits(context.balance, EVMOS_DECIMALS, 2)) - DECIMAL_DISPLAY_FIX, 0), error: () => null, }), + resetBidAmount: assign({ + bidAmount: () => '', + error: () => null, + }), refreshPage: () => { reloadData(); }, @@ -115,8 +119,8 @@ export const biddingStateMachine = setup({ after: { // average block time is 3 seconds, added a bit of buffer here 5000: { - target: 'success', - actions: 'refreshPage', + target: 'idle', + actions: ['refreshPage', 'resetBidAmount'], // Reset bid amount here }, }, }, diff --git a/src/app/api/v1/indexer/auction-end-events/route.ts b/src/app/api/v1/indexer/auction-end-events/route.ts index 5c32362..513bd67 100644 --- a/src/app/api/v1/indexer/auction-end-events/route.ts +++ b/src/app/api/v1/indexer/auction-end-events/route.ts @@ -7,8 +7,9 @@ import { viemPublicClient } from '@/utilities/viem'; import { Log } from '@/utilities/logger'; import { EVMOS_DECIMALS } from '@/constants'; +const MAX_BLOCKS_PER_REQUEST = 10000; const FIRST_AUCTION_BLOCK = process.env.FIRST_AUCTION_BLOCK ? BigInt(process.env.FIRST_AUCTION_BLOCK) : BigInt(0); -const BATCH_SIZE = BigInt(10000); +const BATCH_SIZE = BigInt(MAX_BLOCKS_PER_REQUEST); export async function GET() { try { @@ -45,6 +46,7 @@ export async function GET() { }, burned: event.args.burned.toString(), blockNumber: event.blockNumber.toString(), + // eslint-disable-next-line no-magic-numbers burnedWithoutDecimals: Number(BigInt(event.args.burned) / BigInt(10 ** EVMOS_DECIMALS)), transactionHash: event.transactionHash, transactionIndex: event.transactionIndex, diff --git a/src/app/api/v1/indexer/bid-events/route.ts b/src/app/api/v1/indexer/bid-events/route.ts index 4c86b4a..d0ebc69 100644 --- a/src/app/api/v1/indexer/bid-events/route.ts +++ b/src/app/api/v1/indexer/bid-events/route.ts @@ -6,8 +6,9 @@ import { rpcFetchBiddingHistory } from '@/queries/rpcFetchBiddingHistory'; import { viemPublicClient } from '@/utilities/viem'; import { Log } from '@/utilities/logger'; +const MAX_BLOCKS_PER_REQUEST = 10000; const FIRST_AUCTION_BLOCK = process.env.FIRST_AUCTION_BLOCK ? BigInt(process.env.FIRST_AUCTION_BLOCK) : BigInt(0); -const BATCH_SIZE = BigInt(10000); +const BATCH_SIZE = BigInt(MAX_BLOCKS_PER_REQUEST); export async function GET() { try { diff --git a/src/app/history/[pageNumber]/page.tsx b/src/app/history/[pageNumber]/page.tsx index af2ecdc..e23a5c4 100644 --- a/src/app/history/[pageNumber]/page.tsx +++ b/src/app/history/[pageNumber]/page.tsx @@ -1,10 +1,11 @@ // Copyright Tharsis Labs Ltd.(Evmos) // SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/burn-auction-dapp/blob/main/LICENSE) -import { HistoryContent } from '../_components/HistoryContent'; import { fetchAuctionHistory } from '@/queries/fetchAuctionHistory'; import { PAGINATION_ITEMS_PER_PAGE } from '@/constants'; +import { HistoryContent } from '../_components/HistoryContent'; + const HistoryPaginated = async ({ params }: { params: { pageNumber: number } }) => { const auctionHistory = await fetchAuctionHistory(params.pageNumber, PAGINATION_ITEMS_PER_PAGE); diff --git a/src/app/history/_components/HistoryContent.tsx b/src/app/history/_components/HistoryContent.tsx index 1ec3db4..0665865 100644 --- a/src/app/history/_components/HistoryContent.tsx +++ b/src/app/history/_components/HistoryContent.tsx @@ -2,13 +2,14 @@ // SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/burn-auction-dapp/blob/main/LICENSE) import Image from 'next/image'; -import { AuctionHistoryTable } from './AuctionHistoryTable'; + import { formatUnits } from '@/utilities/formatUnits'; -import { EVMOS_DECIMALS } from '@/constants'; -import Pagination from '../_components/Pagination'; import type { AuctionHistory } from '@/types/AuctionHistory'; import { PAGINATION_ITEMS_PER_PAGE } from '@/constants'; +import Pagination from '../_components/Pagination'; +import { AuctionHistoryTable } from './AuctionHistoryTable'; + export const HistoryContent = ({ auctionHistory, pageNumber }: { auctionHistory: AuctionHistory; pageNumber: number }) => { return (
diff --git a/src/app/history/_components/Pagination.tsx b/src/app/history/_components/Pagination.tsx index babfe23..eed2181 100644 --- a/src/app/history/_components/Pagination.tsx +++ b/src/app/history/_components/Pagination.tsx @@ -1,10 +1,11 @@ // Copyright Tharsis Labs Ltd.(Evmos) // SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/burn-auction-dapp/blob/main/LICENSE) -import { ButtonLink } from '@/components/ui/ButtonLink'; import Image from 'next/image'; import { clsx } from 'clsx'; +import { ButtonLink } from '@/components/ui/ButtonLink'; + interface PaginationProps { currentPage: number; itemsPerPage: number; diff --git a/src/app/history/page.tsx b/src/app/history/page.tsx index b62ff6e..b5e0823 100644 --- a/src/app/history/page.tsx +++ b/src/app/history/page.tsx @@ -1,19 +1,13 @@ // Copyright Tharsis Labs Ltd.(Evmos) // SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/burn-auction-dapp/blob/main/LICENSE) -import Image from 'next/image'; - -import { fetchAuctionHistory } from '@/queries/fetchAuctionHistory'; -import { formatUnits } from '@/utilities/formatUnits'; -import { EVMOS_DECIMALS } from '@/constants'; -import { HistoryContent } from './_components/HistoryContent'; import { fetchAuctionHistory } from '@/queries/fetchAuctionHistory'; import { PAGINATION_ITEMS_PER_PAGE } from '@/constants'; -import { AuctionHistoryTable } from './_components/AuctionHistoryTable'; +import { HistoryContent } from './_components/HistoryContent'; const History = async () => { - const auctionHistory = await fetchAuctionHistory(1, PAGINATION_ITEMS_PER_PAGE); + let auctionHistory = await fetchAuctionHistory('last', PAGINATION_ITEMS_PER_PAGE); const totalItems = auctionHistory.totalItems; const itemsPerPage = PAGINATION_ITEMS_PER_PAGE; const totalPages = Math.ceil(totalItems / itemsPerPage); diff --git a/src/constants/index.ts b/src/constants/index.ts index b3f7226..8a71c39 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -15,4 +15,5 @@ export const UNKNOWN_TOKEN_METADATA_DEFAULT = { amountWithDecimals: 0, }; +// eslint-disable-next-line no-magic-numbers export const PAGINATION_ITEMS_PER_PAGE = process.env.PAGINATION_ITEMS_PER_PAGE ? parseInt(process.env.PAGINATION_ITEMS_PER_PAGE) : 10; diff --git a/src/queries/__tests__/fetchAuctionHistory.spec.ts b/src/queries/__tests__/fetchAuctionHistory.spec.ts index 986d950..4beacdf 100644 --- a/src/queries/__tests__/fetchAuctionHistory.spec.ts +++ b/src/queries/__tests__/fetchAuctionHistory.spec.ts @@ -4,11 +4,11 @@ import { expect, describe, it, expectTypeOf, beforeEach, afterEach, vi } from 'vitest'; import type { AuctionHistory } from '@/types/AuctionHistory'; +import { EVMOS_DECIMALS } from '@/constants'; import { fetchAuctionHistory } from '../fetchAuctionHistory'; import { mockAuctionEndEvents } from './mockedData'; import * as prismaModule from '../prismaFetchAuctionEvents'; -import { EVMOS_DECIMALS } from '@/constants'; // Mock the entire module vi.mock('../prismaFetchAuctionEvents'); @@ -30,6 +30,7 @@ afterEach(() => { vi.clearAllMocks(); }); +/* eslint-disable no-magic-numbers */ describe('fetchAuctionHistory()', () => { it('should return the auction history and the correct total burned amount', async () => { mockPrismaFetchAuctionEvents.mockResolvedValue( diff --git a/src/queries/__tests__/fetchBiddingHistory.spec.ts b/src/queries/__tests__/fetchBiddingHistory.spec.ts index 8edb38c..9f1d5d1 100644 --- a/src/queries/__tests__/fetchBiddingHistory.spec.ts +++ b/src/queries/__tests__/fetchBiddingHistory.spec.ts @@ -6,14 +6,13 @@ import { expect, describe, it, expectTypeOf, beforeEach, afterEach, vi } from 'v import type { BiddingHistory } from '@/types/BiddingHistory'; import { fetchBiddingHistory } from '../fetchBiddingHistory'; -import { mockBiddingHistoryResponse } from './mockedData'; -import type { BiddingHistory } from '@/types/BiddingHistory'; import { prismaFetchBidEvent } from '../prismaFetchBidEvent'; import { rpcFetchBlockDate } from '../rpcFetchBlockDate'; vi.mock('../prismaFetchBidEvent'); vi.mock('../rpcFetchBlockDate'); +/* eslint-disable no-magic-numbers */ describe('fetchBiddingHistory(round)', () => { beforeEach(() => { vi.resetAllMocks(); diff --git a/src/queries/__tests__/fetchCurrentAuction.spec.ts b/src/queries/__tests__/fetchCurrentAuction.spec.ts index 8bea453..7410035 100644 --- a/src/queries/__tests__/fetchCurrentAuction.spec.ts +++ b/src/queries/__tests__/fetchCurrentAuction.spec.ts @@ -3,7 +3,6 @@ import { expect, describe, it, expectTypeOf, beforeEach, afterEach, vi } from 'vitest'; -import { fetchCurrentAuction } from '../fetchCurrentAuction'; import type { AuctionDetailed } from '@/types/AuctionDetailed'; import { fetchCurrentAuction } from '../fetchCurrentAuction'; diff --git a/src/queries/__tests__/fetchPastAuction.spec.ts b/src/queries/__tests__/fetchPastAuction.spec.ts index b970e45..d1af86b 100644 --- a/src/queries/__tests__/fetchPastAuction.spec.ts +++ b/src/queries/__tests__/fetchPastAuction.spec.ts @@ -4,14 +4,13 @@ import { expect, describe, it, expectTypeOf, beforeEach, afterEach, vi } from 'vitest'; import type { AuctionDetailed } from '@/types/AuctionDetailed'; +import { fetchChainRegistryDir } from '@/utilities/fetchChainRegistryDir'; +import { EVMOS_DECIMALS } from '@/constants'; import { fetchPastAuction } from '../fetchPastAuction'; -import { mockCoinGeckoResponse, mockAuctionResponse, epochInfoResponse, mockAuctionEndEventsRound3 } from './mockedData'; import { prismaFetchAuctionEvent } from '../prismaFetchAuctionEvent'; import { fetchAuctionDates } from '../fetchAuctionDates'; import { fetchPastCryptoPrice } from '../fetchPastCryptoPrice'; -import { fetchChainRegistryDir } from '@/utilities/fetchChainRegistryDir'; -import { EVMOS_DECIMALS } from '@/constants'; vi.mock('../prismaFetchAuctionEvent'); vi.mock('../fetchAuctionDates'); @@ -29,6 +28,7 @@ afterEach(() => { vi.clearAllMocks(); }); +/* eslint-disable no-magic-numbers */ describe('fetchPastAuction()', () => { it('should return the past auction info of type AuctionDetailed', async () => { const mockAuctionEvent = { @@ -52,6 +52,7 @@ describe('fetchPastAuction()', () => { vi.mocked(prismaFetchAuctionEvent).mockResolvedValue([ { ...mockAuctionEvent, + // @ts-ignore coins: mockAuctionEvent.coins.map((coin) => ({ ...coin, id: 1, @@ -131,6 +132,7 @@ describe('fetchPastAuction()', () => { vi.mocked(prismaFetchAuctionEvent).mockResolvedValue([ { ...mockAuctionEvent, + // @ts-ignore coins: mockAuctionEvent.coins.map((coin) => ({ ...coin, id: 1, @@ -179,6 +181,7 @@ describe('fetchPastAuction()', () => { vi.mocked(prismaFetchAuctionEvent).mockResolvedValue([ { ...mockAuctionEvent, + // @ts-ignore coins: mockAuctionEvent.coins.map((coin) => ({ ...coin, id: 1, @@ -192,12 +195,6 @@ describe('fetchPastAuction()', () => { }); vi.mocked(fetchChainRegistryDir).mockRejectedValue(new Error('Failed to fetch chain registry')); -describe('fetchPastAuction(5)', async () => { - it('should return the current auction info of type AuctionDetailed', async () => { - // eslint-disable-next-line no-magic-numbers - const result = await fetchPastAuction(BigInt(40)); - expect(result).toBeDefined(); - expectTypeOf(result).toMatchTypeOf(); await expect(fetchPastAuction(BigInt(6))).rejects.toThrow('Failed to fetch chain registry'); }); }); diff --git a/src/queries/__tests__/rpcFetchAuctionEnd.spec.ts b/src/queries/__tests__/rpcFetchAuctionEnd.spec.ts index f8ef986..91dbdef 100644 --- a/src/queries/__tests__/rpcFetchAuctionEnd.spec.ts +++ b/src/queries/__tests__/rpcFetchAuctionEnd.spec.ts @@ -6,7 +6,7 @@ import { expect, describe, it, expectTypeOf, beforeEach, afterEach, vi } from 'v import type { AuctionEndEvent } from '@/types/AuctionEndEvent'; import { rpcFetchAuctionEnd } from '../rpcFetchAuctionEnd'; -import { mockAuctionEndEvents, mockAuctionEndEventsRound3 } from './mockedData'; +import { mockAuctionEndEvents } from './mockedData'; beforeEach(() => { vi.mock('../rpcFetchAuctionEnd', async (importOriginal) => { @@ -14,7 +14,7 @@ beforeEach(() => { return { // @ts-ignore ...actual, - rpcFetchAuctionEnd: vi.fn((from, to) => mockAuctionEndEvents), + rpcFetchAuctionEnd: vi.fn(() => mockAuctionEndEvents), }; }); }); @@ -23,6 +23,7 @@ afterEach(() => { vi.clearAllMocks(); }); +/* eslint-disable no-magic-numbers */ describe('rpcFetchAuctionEnd(from, to)', async () => { it('should return all the AuctionEnd events', async () => { const result = await rpcFetchAuctionEnd(BigInt(1), BigInt(3)); @@ -30,14 +31,3 @@ describe('rpcFetchAuctionEnd(from, to)', async () => { expectTypeOf(result).toMatchTypeOf(); }); }); - -describe('rpcFetchAuctionEnd(round)', async () => { - it('should return a specific AuctionEnd event', async () => { - // eslint-disable-next-line no-magic-numbers - const result = await rpcFetchAuctionEnd(BigInt(3)); - expect(result).toBeDefined(); - expect(result.length).toEqual(1); - expect(result.length).toEqual(2); - expectTypeOf(result).toMatchTypeOf(); - }); -}); diff --git a/src/queries/__tests__/rpcFetchBiddingHistory.spec.ts b/src/queries/__tests__/rpcFetchBiddingHistory.spec.ts index 8ffc74b..abd3c28 100644 --- a/src/queries/__tests__/rpcFetchBiddingHistory.spec.ts +++ b/src/queries/__tests__/rpcFetchBiddingHistory.spec.ts @@ -21,6 +21,7 @@ afterEach(() => { vi.clearAllMocks(); }); +/* eslint-disable no-magic-numbers */ describe('rpcFetchBiddingHistory(from, to)', async () => { it('should return the Bids for a given round number', async () => { const result = await rpcFetchBiddingHistory(BigInt(1), BigInt(3)); diff --git a/src/queries/fetchAuctionHistory.ts b/src/queries/fetchAuctionHistory.ts index c87235a..45ac0b4 100644 --- a/src/queries/fetchAuctionHistory.ts +++ b/src/queries/fetchAuctionHistory.ts @@ -1,18 +1,23 @@ // Copyright Tharsis Labs Ltd.(Evmos) // SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/burn-auction-dapp/blob/main/LICENSE) - -import { prismaFetchAuctionEvents } from './prismaFetchAuctionEvents'; - import { E } from '@/utilities/error-handling'; import { Log } from '@/utilities/logger'; import type { AuctionHistory } from '@/types/AuctionHistory'; import { HexAddress } from '@/types/HexAddress'; +import { prismaFetchAuctionEvents } from './prismaFetchAuctionEvents'; -export const fetchAuctionHistory = async (page: number, itemsPerPage: number): Promise => { - const [error, auctionEvents] = await E.try(() => prismaFetchAuctionEvents(page, itemsPerPage)); +export const fetchAuctionHistory = async (page: number | 'last', itemsPerPage: number): Promise => { + const [errorTotalItems, totalItems] = await E.try(() => prismaFetchAuctionEvents.count()); + if (errorTotalItems) { + Log().error('Error fetching total items:', errorTotalItems); + throw errorTotalItems; + } + const lastPage = Math.ceil(totalItems / itemsPerPage); + + const [error, auctionEvents] = await E.try(() => prismaFetchAuctionEvents(page === 'last' ? lastPage : page, itemsPerPage)); if (error) { Log().error('Error fetching events date:', error); throw error; @@ -27,12 +32,6 @@ export const fetchAuctionHistory = async (page: number, itemsPerPage: number): P }; }); - const [errorTotalItems, totalItems] = await E.try(() => prismaFetchAuctionEvents.count()); - if (errorTotalItems) { - Log().error('Error fetching total items:', errorTotalItems); - throw error; - } - const [errorTotalBurned, totalBurned] = await E.try(() => prismaFetchAuctionEvents.totalBurned()); if (errorTotalBurned) { Log().error('Error fetching total burned:', errorTotalBurned); diff --git a/src/queries/fetchBiddingHistory.ts b/src/queries/fetchBiddingHistory.ts index ed15e10..4f9e9cb 100644 --- a/src/queries/fetchBiddingHistory.ts +++ b/src/queries/fetchBiddingHistory.ts @@ -1,16 +1,13 @@ // Copyright Tharsis Labs Ltd.(Evmos) // SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/burn-auction-dapp/blob/main/LICENSE) - -import { prismaFetchBidEvent } from './prismaFetchBidEvent'; -import { rpcFetchBlockDate } from './rpcFetchBlockDate'; import { E } from '@/utilities/error-handling'; import { Log } from '@/utilities/logger'; import type { BiddingHistory } from '@/types/BiddingHistory'; import { HexAddress } from '@/types/HexAddress'; import { rpcFetchBlockDate } from './rpcFetchBlockDate'; -import { rpcFetchBiddingHistory } from './rpcFetchBiddingHistory'; +import { prismaFetchBidEvent } from './prismaFetchBidEvent'; export const fetchBiddingHistory = async (round: bigint): Promise => { const [error, biddingEvents] = await E.try(() => prismaFetchBidEvent(round)); diff --git a/src/queries/fetchCurrentAuction.ts b/src/queries/fetchCurrentAuction.ts index 43730cb..d06240a 100644 --- a/src/queries/fetchCurrentAuction.ts +++ b/src/queries/fetchCurrentAuction.ts @@ -35,7 +35,7 @@ export const fetchCurrentAuction = async (): Promise => { throw errorEndDate; } - const currentAuctionInfo = { + const currentAuctionInfo: AuctionDetailed = { round: { round: auctionInfo.currentRound, isLast: true, @@ -53,6 +53,7 @@ export const fetchCurrentAuction = async (): Promise => { assets: [] as AuctionnedAsset[], totalValue: 0, hasPriceError: false, + evmosToUsdRate: 0, }, }; @@ -103,6 +104,7 @@ export const fetchCurrentAuction = async (): Promise => { return asset; }); + currentAuctionInfo.auction.evmosToUsdRate = prices['evmos']['usd']; currentAuctionInfo.highestBid.bidInUsd = currentAuctionInfo.highestBid.bidInEvmosWithDecimals * prices['evmos']['usd']; currentAuctionInfo.auction.totalValue = currentAuctionInfo.auction.assets.reduce((acc, asset) => acc + asset.valueInUsd, 0); diff --git a/src/queries/fetchCurrentCryptoPrice.ts b/src/queries/fetchCurrentCryptoPrice.ts index c828fbb..e8d6ea5 100644 --- a/src/queries/fetchCurrentCryptoPrice.ts +++ b/src/queries/fetchCurrentCryptoPrice.ts @@ -12,6 +12,7 @@ type CryptoPrice = { }; const MOCK_COINGECKO_API = process.env.MOCK_COINGECKO_API === 'true'; +const STATUS_OK = 200; export const fetchCurrentCryptoPrice = async (ids: string[]): Promise => { // To avoid hitting the rate limit of the Coingecko API @@ -31,7 +32,7 @@ export const fetchCurrentCryptoPrice = async (ids: string[]): Promise fetch(`https://api.coingecko.com/api/v3/simple/price?ids=${coingeckoIds}&vs_currencies=usd`, { next: { revalidate: 60 } })); Log().info('Fetching crypto price:', coingeckoIds); - if (error) { + if (error || result.status !== STATUS_OK) { Log().error('Error fetching crypto price:', error); return Object.fromEntries(ids.map((id) => [id, { usd: 0, error: true }])); } diff --git a/src/queries/fetchPastAuction.ts b/src/queries/fetchPastAuction.ts index 8c401ee..87ce6fa 100644 --- a/src/queries/fetchPastAuction.ts +++ b/src/queries/fetchPastAuction.ts @@ -8,11 +8,11 @@ import { AuctionnedAsset } from '@/types/AuctionnedAsset'; import { fetchChainRegistryDir } from '@/utilities/fetchChainRegistryDir'; import { TokenEntity } from '@/utilities//registry/autogen/token-entity'; import { EVMOS_DECIMALS, UNKNOWN_TOKEN_METADATA_DEFAULT } from '@/constants'; +import type { HexAddress } from '@/types/HexAddress'; + import { fetchAuctionDates } from './fetchAuctionDates'; -import { rpcFetchAuctionEnd } from './rpcFetchAuctionEnd'; import { fetchPastCryptoPrice } from './fetchPastCryptoPrice'; import { prismaFetchAuctionEvent } from './prismaFetchAuctionEvent'; -import type { HexAddress } from '@/types/HexAddress'; export const fetchPastAuction = async (round: bigint): Promise => { const [error, auctionEndEvent] = await E.try(() => prismaFetchAuctionEvent(round)); @@ -59,6 +59,7 @@ export const fetchPastAuction = async (round: bigint): Promise assets: [] as AuctionnedAsset[], totalValue: 0, hasPriceError: false, + evmosToUsdRate: 0, }, }; @@ -71,7 +72,7 @@ export const fetchPastAuction = async (round: bigint): Promise ...UNKNOWN_TOKEN_METADATA_DEFAULT, denom: token.denom, amount: BigInt(token.amount), - priceError: true + priceError: true, }; if (!tokenMetadata) { @@ -108,8 +109,13 @@ export const fetchPastAuction = async (round: bigint): Promise auctionDetails.auction.assets.push(asset); } + // eslint-disable-next-line no-unused-vars + const [_, evmosToUsdRateCoingecko] = await E.try(() => fetchPastCryptoPrice('evmos', dates.end)); + const evmosToUsdRate = evmosToUsdRateCoingecko ?? 0; + + auctionDetails.auction.evmosToUsdRate = evmosToUsdRate; auctionDetails.auction.totalValue = totalValue; - auctionDetails.highestBid.bidInUsd = (await fetchPastCryptoPrice('evmos', dates.end)) * auctionDetails.highestBid.bidInEvmosWithDecimals; + auctionDetails.highestBid.bidInUsd = auctionDetails.auction.evmosToUsdRate * auctionDetails.highestBid.bidInEvmosWithDecimals; return auctionDetails; }; diff --git a/src/queries/fetchPastCryptoPrice.ts b/src/queries/fetchPastCryptoPrice.ts index 60839fd..167aa66 100644 --- a/src/queries/fetchPastCryptoPrice.ts +++ b/src/queries/fetchPastCryptoPrice.ts @@ -7,6 +7,7 @@ import { Log } from '@/utilities/logger'; const MOCK_COINGECKO_API = process.env.MOCK_COINGECKO_API === 'true'; // eslint-disable-next-line no-magic-numbers const ONE_YEAR_IN_SECONDS = 60 * 60 * 24 * 365; +const STATUS_OK = 200; export const fetchPastCryptoPrice = async (coinId: string, date: Date): Promise => { // To avoid hitting the rate limit of the Coingecko API @@ -20,7 +21,7 @@ export const fetchPastCryptoPrice = async (coinId: string, date: Date): Promise< const [error, result] = await E.try(() => fetch(`https://api.coingecko.com/api/v3/coins/${coinId}/history?date=${formattedDate}`, { next: { revalidate: ONE_YEAR_IN_SECONDS } })); Log().info('Fetching crypto price:', { coinId, date }); - if (error) { + if (error || result.status !== STATUS_OK) { Log().error('Error fetching crypto price:', error); throw error; } diff --git a/src/queries/prismaFetchAuctionEvent.ts b/src/queries/prismaFetchAuctionEvent.ts index 2b4dc2f..03655d4 100644 --- a/src/queries/prismaFetchAuctionEvent.ts +++ b/src/queries/prismaFetchAuctionEvent.ts @@ -11,9 +11,7 @@ export const prismaFetchAuctionEvent = async (round: bigint) => { round: 'asc', }, where: { - where: { - round: BigInt(round), - }, + round: Number(round), }, include: { coins: true, diff --git a/src/types/AuctionDetailed.ts b/src/types/AuctionDetailed.ts index 8c636fe..dc002bc 100644 --- a/src/types/AuctionDetailed.ts +++ b/src/types/AuctionDetailed.ts @@ -15,6 +15,7 @@ export type AuctionDetailed = { assets: AuctionnedAsset[]; totalValue: number; hasPriceError: boolean; + evmosToUsdRate: number; }; highestBid: { bidInEvmos: bigint; diff --git a/src/utilities/fetchChainRegistryDir.ts b/src/utilities/fetchChainRegistryDir.ts index 6ddeff8..38b6972 100644 --- a/src/utilities/fetchChainRegistryDir.ts +++ b/src/utilities/fetchChainRegistryDir.ts @@ -4,10 +4,10 @@ 'use server'; import { isString } from '@/utilities/assertions'; -const GITHUB_API_PROXY = process.env.GITHUB_API_PROXY; +const GH_API_PROXY = process.env.GH_API_PROXY; async function getContent(owner: string, repo: string, path: string, ref: string) { - return fetch(GITHUB_API_PROXY + 'repos/' + owner + '/' + repo + '/contents/' + path + '?ref=' + ref); + return fetch(GH_API_PROXY + 'repos/' + owner + '/' + repo + '/contents/' + path + '?ref=' + ref); } export const fetchChainRegistryDir = async (dir: string) => {