Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
},
"dependencies": {
"@hookform/resolvers": "^3.9.0",
"@paywithglide/glide-js": "^0.9.24",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-popover": "^1.1.1",
Expand Down
4 changes: 2 additions & 2 deletions src/app/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

import { getDefaultConfig, RainbowKitProvider } from "@rainbow-me/rainbowkit";
import { WagmiProvider } from "wagmi";
import { optimism } from "wagmi/chains";
import { base, optimism } from "wagmi/chains";
import { QueryClientProvider, QueryClient } from "@tanstack/react-query";

const queryClient = new QueryClient();

const config = getDefaultConfig({
appName: "Jellybeans",
projectId: process.env.NEXT_PUBLIC_WC_ID || "YOUR_PROJECT_ID",
chains: [optimism],
chains: [optimism, base],
ssr: true,
});

Expand Down
96 changes: 83 additions & 13 deletions src/app/round/[num]/_components/submit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,24 @@ import { Card, CardContent } from "@/components/ui/card";
import { Lightbulb } from "lucide-react";
import { toast } from "sonner";

import { formatEther } from "viem";
import { formatEther, Hex } from "viem";

import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { Control, useForm, useWatch } from "react-hook-form";

import { writeContract, waitForTransactionReceipt } from "@wagmi/core";
import { useAccount, useConfig } from "wagmi";
import { waitForTransactionReceipt, writeContract } from "@wagmi/core";
import { useAccount, useChainId, useConfig, useSendTransaction, useSwitchChain } from "wagmi";
import { JellyBeansAbi } from "@/constants/JellyBeansAbi";
import { JELLYBEANS_ADDRESS } from "@/constants/contracts";
import { useCountdownPassed } from "@/lib/hooks";
import { useQueryClient } from "@tanstack/react-query";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { type ActiveRoundData, type RawSubmissionsData } from "@/lib/types";
import { bigintDateNow } from "@/lib/utils";
import { AmountUSD } from "@/components/amount-usd";
import { chains, createSession, executeSession } from "@paywithglide/glide-js";
import { glideConfig } from "@/lib/clients/glide";
import { useEffect, useRef, useState } from "react";

class NotConnectedError extends Error {
constructor(message: string) {
Expand Down Expand Up @@ -59,6 +62,27 @@ const guessFormSchema = z.object({
});
type GuessForm = z.infer<typeof guessFormSchema>;

// useDebouncedGuess returns guess value changes every 500ms. This avoids unnecessary network
// requests when the user is still making changes.
// It returns the guess value and `isDirty` which is set to true when the debounced value is
// different from the actual value.
const useDebouncedGuess = ({ control }: { control: Control<{ guess: number }> }) => {
const [debouncedGuess, setDebouncedGuess] = useState(control._defaultValues.guess || 0);
const timeout = useRef<NodeJS.Timeout>();

const guess = useWatch({ name: "guess", control });

useEffect(() => {
clearInterval(timeout.current);

timeout.current = setTimeout(() => {
setDebouncedGuess(guess);
}, 500);
}, [guess]);

return { guess: debouncedGuess, isDirty: guess !== debouncedGuess };
};

function SubmitForm({
round,
feeAmount,
Expand All @@ -76,22 +100,63 @@ function SubmitForm({

const { address, isConnected } = useAccount();
const queryClient = useQueryClient();
const currentChainId = useChainId();
const { switchChainAsync } = useSwitchChain();
const { sendTransactionAsync } = useSendTransaction();

const isSubmissionPassed = useCountdownPassed(submissionDeadline);

const { guess, isDirty: isGuessDirty } = useDebouncedGuess({ control: form.control });

const { data: glideSession, isSuccess: isGlideSessionReady } = useQuery({
queryKey: ["glideSession", round, guess],
queryFn: async () => {
const session = await createSession(glideConfig, {
account: address,
chainId: chains.optimism.id,
address: JELLYBEANS_ADDRESS,
abi: JellyBeansAbi,
functionName: "submitGuess",
args: [BigInt(round), BigInt(guess)],
value: feeAmount,
});

return session;
},
// Ensure a new session is created every 30s to avoid expired sessions
refetchInterval: 30000,
});

async function onSubmit(values: GuessForm) {
console.log(values);

try {
if (!isConnected) throw new NotConnectedError("Connect wallet to submit.");
if (!glideSession) throw new Error("Transaction is not ready yet.");

let hash: Hex;
// If the user has funds on OP, continue with a direct `writeContract` call.
// Else, we pay using Glide with cross-chain payment.
if (glideSession.paymentChainId === `eip155:10`) {
hash = await writeContract(config, {
abi: JellyBeansAbi,
address: JELLYBEANS_ADDRESS,
functionName: "submitGuess",
args: [BigInt(round), BigInt(values.guess)],
value: feeAmount,
});
} else {
const { sponsoredTransactionHash } = await executeSession(glideConfig, {
session: glideSession,
// @ts-expect-error: wagmi types are not set correctly
currentChainId,
sendTransactionAsync,
switchChainAsync,
});

hash = sponsoredTransactionHash;
}

const hash = await writeContract(config, {
abi: JellyBeansAbi,
address: JELLYBEANS_ADDRESS,
functionName: "submitGuess",
args: [BigInt(round), BigInt(values.guess)],
value: feeAmount,
});
toast.message("Pending confirmation...");

queryClient.setQueryData(["round-data", round], (old: ActiveRoundData | undefined) => ({
Expand Down Expand Up @@ -181,7 +246,12 @@ function SubmitForm({
<Button
variant="secondary"
type="submit"
disabled={form.formState.isSubmitting || isSubmissionPassed}
disabled={
!isGlideSessionReady ||
isGuessDirty ||
form.formState.isSubmitting ||
isSubmissionPassed
}
>
Submit
</Button>
Expand Down
12 changes: 12 additions & 0 deletions src/lib/clients/glide.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { createGlideConfig, chains, currencies } from "@paywithglide/glide-js";

export const glideConfig = createGlideConfig({
projectId: process.env.NEXT_PUBLIC_GLIDE_PROJECT_ID!,

// Add more chains as needed. Remember to add them to wagmi config as well.
chains: [chains.base, chains.optimism],

// Optional. Remove this filter if we want to support non-ETH currencies as
// well. Or, add more supported currencies such as currencies.usdc here.
paymentCurrencies: [currencies.eth],
});
Loading