diff --git a/.env.example b/.env.example
index ece6795..7f594bd 100644
--- a/.env.example
+++ b/.env.example
@@ -1,2 +1,4 @@
NEXT_PUBLIC_REOWN_PROJECTID=
NEXT_PUBLIC_GNOSIS_RPC=
+# https://thegraph.com/explorer/subgraphs/AAA1vYjxwFHzbt6qKwLHNcDSASyr1J1xVViDH8gTMFMR?view=Query&chain=arbitrum-one
+NEXT_PUBLIC_ALGEBRA_SUBGRAPH=
diff --git a/.eslintrc.json b/.eslintrc.json
index 84e1f88..d9dd64a 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -5,7 +5,7 @@
"plugin:prettier/recommended",
"prettier"
],
- "ignorePatterns": ["**/**/generated.ts"],
+ "ignorePatterns": ["**/generated.ts", "src/hooks/liquidity/gql/*"],
"rules": {
"max-len": [
"warn",
diff --git a/.gitignore b/.gitignore
index 8cd6d95..09e6c75 100644
--- a/.gitignore
+++ b/.gitignore
@@ -37,3 +37,6 @@ next-env.d.ts
# wagmi generated
/src/generated.ts
+
+# gql
+/src/hooks/liquidity/gql
diff --git a/codegen.ts b/codegen.ts
new file mode 100644
index 0000000..538db2d
--- /dev/null
+++ b/codegen.ts
@@ -0,0 +1,29 @@
+import { CodegenConfig } from "@graphql-codegen/cli";
+
+const config: CodegenConfig = {
+ overwrite: true,
+ schema: [process.env.NEXT_PUBLIC_ALGEBRA_SUBGRAPH!],
+ documents: ["src/hooks/liquidity/swapr.graphql"],
+ generates: {
+ "./src/hooks/liquidity/gql/gql.ts": {
+ // preset: "client",
+ plugins: [
+ "typescript",
+ "typescript-operations",
+ "typescript-graphql-request",
+ ],
+ config: {
+ strictScalars: true,
+ scalars: {
+ BigDecimal: "string",
+ BigInt: "string",
+ Int8: "string",
+ Bytes: "`0x${string}`",
+ Timestamp: "string",
+ },
+ },
+ },
+ },
+};
+
+export default config;
diff --git a/package.json b/package.json
index 73f1964..f6413cf 100644
--- a/package.json
+++ b/package.json
@@ -4,10 +4,11 @@
"private": true,
"scripts": {
"dev": "yarn generate && next dev",
- "build": "next build",
+ "build": "yarn generate && next build",
"start": "next start",
"lint": "next lint --fix",
- "generate": "wagmi generate"
+ "generate": "wagmi generate && yarn generate:gql",
+ "generate:gql": "dotenv -e .env.local -- graphql-codegen"
},
"dependencies": {
"@cowprotocol/cow-sdk": "^5.10.3",
@@ -17,10 +18,13 @@
"@swapr/sdk": "https://github.com/seer-pm/swapr-sdk#6dea7e63f7e05c84a4374717ee1ad5baca86f7de",
"@tanstack/react-query": "^5.74.4",
"@wagmi/core": "^2.17.3",
+ "@yornaath/batshit": "^0.11.1",
"clsx": "^2.1.1",
"ethers": "5.8.0",
+ "graphql-request": "^7.3.1",
"graphql-tag": "^2.12.6",
"lightweight-charts": "^5.0.8",
+ "micro-memoize": "^4.2.0",
"next": "14.2.28",
"next-themes": "^0.4.6",
"pino-pretty": "^13.0.0",
@@ -33,12 +37,17 @@
"wagmi": "^2.15.6"
},
"devDependencies": {
+ "@graphql-codegen/cli": "^5.0.2",
+ "@graphql-codegen/typescript": "^5.0.2",
+ "@graphql-codegen/typescript-graphql-request": "^6.3.0",
+ "@graphql-codegen/typescript-operations": "^5.0.2",
"@svgr/webpack": "^8.1.0",
"@tailwindcss/postcss": "^4.1.4",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"@wagmi/cli": "^2.3.1",
+ "dotenv-cli": "^10.0.0",
"eslint": "^8",
"eslint-config-next": "14.2.28",
"eslint-config-prettier": "^10.1.2",
diff --git a/public/futarchy-kleros.png b/public/futarchy-kleros.png
new file mode 100644
index 0000000..e32361b
Binary files /dev/null and b/public/futarchy-kleros.png differ
diff --git a/public/futarchy_kleros.png b/public/futarchy_kleros.png
deleted file mode 100644
index 29862da..0000000
Binary files a/public/futarchy_kleros.png and /dev/null differ
diff --git a/public/web-app-manifest-192x192.png b/public/web-app-manifest-192x192.png
new file mode 100644
index 0000000..02d3313
Binary files /dev/null and b/public/web-app-manifest-192x192.png differ
diff --git a/public/web-app-manifest-512x512.png b/public/web-app-manifest-512x512.png
new file mode 100644
index 0000000..1791019
Binary files /dev/null and b/public/web-app-manifest-512x512.png differ
diff --git a/src/app/(homepage)/components/AdvancedSection.tsx b/src/app/(homepage)/components/AdvancedSection.tsx
index 26760ee..449c1c5 100644
--- a/src/app/(homepage)/components/AdvancedSection.tsx
+++ b/src/app/(homepage)/components/AdvancedSection.tsx
@@ -25,13 +25,13 @@ const AdvancedSection: React.FC = () => {
tokens in Seer.
- Check it out
+ Check it out
diff --git a/src/app/(homepage)/components/Chart/index.tsx b/src/app/(homepage)/components/Chart/index.tsx
index 5a0b6c5..a358600 100644
--- a/src/app/(homepage)/components/Chart/index.tsx
+++ b/src/app/(homepage)/components/Chart/index.tsx
@@ -81,7 +81,7 @@ const Chart: React.FC<{ data: IChartData[] }> = ({ data }) => {
const latestTimestamp = Date.now() / 1000;
// Generate common timestamps for all markets
- const timestamps = getTimestamps(1754407213, latestTimestamp);
+ const timestamps = getTimestamps(1751328000, latestTimestamp);
const seriesData: Record<
string,
diff --git a/src/app/(homepage)/components/Header/index.tsx b/src/app/(homepage)/components/Header/index.tsx
index 9cd8098..13ff5ea 100644
--- a/src/app/(homepage)/components/Header/index.tsx
+++ b/src/app/(homepage)/components/Header/index.tsx
@@ -12,7 +12,7 @@ const Header: React.FC = () => {
return (
- Session 1 - Retro PGF Experiment
+ Session 1 - Movies Experiment
@@ -41,7 +41,7 @@ const Header: React.FC = () => {
- If rated, what score would the gourmet committee give to the meal?
+ If watched, what score will Clément give to the movie?
diff --git a/src/app/(homepage)/components/ParticipateSection/Mint/MergeButton.tsx b/src/app/(homepage)/components/ParticipateSection/Mint/MergeButton.tsx
deleted file mode 100644
index 9bd89be..0000000
--- a/src/app/(homepage)/components/ParticipateSection/Mint/MergeButton.tsx
+++ /dev/null
@@ -1,153 +0,0 @@
-import React, { useMemo } from "react";
-
-import { Button } from "@kleros/ui-components-library";
-import { waitForTransactionReceipt } from "@wagmi/core";
-import { encodeFunctionData, erc20Abi, Address } from "viem";
-import { useConfig, useSendCalls, useCapabilities } from "wagmi";
-
-import {
- gnosisRouterAddress,
- gnosisRouterAbi,
- sDaiAddress,
- useWriteErc20Approve,
- useWriteGnosisRouterMergePositions,
-} from "@/generated";
-
-import { useTokenAllowances } from "@/hooks/useTokenAllowances";
-
-import { parentMarket, invalidMarket, markets } from "@/consts/markets";
-
-interface IMergeButton {
- amount: bigint;
- isMinting: boolean;
- toggleIsMinting: (value: boolean) => void;
- refetchSDai: () => void;
- refetchBalances: () => void;
-}
-
-const MergeButton: React.FC
= ({
- amount,
- isMinting,
- toggleIsMinting,
- refetchSDai,
- refetchBalances,
-}) => {
- const wagmiConfig = useConfig();
- const { sendCalls } = useSendCalls();
-
- const atomicSupport = false;
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- const { data: capabilities } = useCapabilities();
- // const atomicSupport = useMemo(
- // () =>
- // ["ready", "supported"].includes(capabilities?.[100].atomic?.status ?? ""),
- // [capabilities],
- // );
-
- const allowances = useTokenAllowances(
- markets
- .map(({ underlyingToken }) => underlyingToken)
- .concat([invalidMarket]),
- gnosisRouterAddress,
- );
-
- const needApproval = useMemo(() => {
- const queryKey = allowances.queryKey as readonly [
- unknown,
- { contracts: { address: Address }[] },
- ];
- if (typeof allowances?.data !== "undefined") {
- return allowances.data
- .map(({ result }, i) => ({
- address: queryKey[1].contracts[i].address,
- result,
- }))
- .filter(({ result }) => typeof result === "bigint" && result < amount)
- .map(({ address }) => address);
- }
- return [];
- }, [allowances, amount]);
-
- const calls = useMemo(() => {
- const calls = needApproval.map((address) => ({
- to: address,
- value: 0n,
- data: encodeFunctionData({
- abi: erc20Abi,
- functionName: "approve",
- args: [gnosisRouterAddress, amount],
- }),
- }));
- calls.push({
- to: gnosisRouterAddress,
- value: 0n,
- data: encodeFunctionData({
- abi: gnosisRouterAbi,
- functionName: "mergePositions",
- args: [sDaiAddress, parentMarket, amount],
- }),
- });
- return calls;
- }, [amount, needApproval]);
-
- const { writeContractAsync: approve } = useWriteErc20Approve();
- const { writeContractAsync: mergePositions } =
- useWriteGnosisRouterMergePositions();
-
- return (
-
- {isResolved ?
: null}
+ {isResolved ?
: null}
);
};
@@ -138,14 +136,11 @@ const useTokenPositionValue = (
) => {
const { hasLiquidity, marketPrice } = useMarketContext();
- const { data: balance } = useReadErc20BalanceOf({
- address: token,
- args: [address ?? "0x"],
- query: {
- staleTime: 5000,
- enabled: typeof address !== "undefined",
- },
+ const { data: balanceData } = useTokenBalance({
+ token: token,
+ address: address,
});
+ const balance = balanceData?.value;
const { data } = useMarketPrice(
token,
diff --git a/src/app/(homepage)/components/ProjectFunding/PredictButton.tsx b/src/app/(homepage)/components/ProjectFunding/PredictButton.tsx
index 1a92cca..09139d3 100644
--- a/src/app/(homepage)/components/ProjectFunding/PredictButton.tsx
+++ b/src/app/(homepage)/components/ProjectFunding/PredictButton.tsx
@@ -1,127 +1,25 @@
+"use client";
import React from "react";
-import { useMemo } from "react";
-import { Button, Modal } from "@kleros/ui-components-library";
+import { Button } from "@kleros/ui-components-library";
import { useToggle } from "react-use";
-import { formatUnits } from "viem";
-import { useCardInteraction } from "@/context/CardInteractionContext";
import { useMarketContext } from "@/context/MarketContext";
-import { useBalance } from "@/hooks/useBalance";
-import { useMarketQuote } from "@/hooks/useMarketQuote";
-import LightButton from "@/components/LightButton";
+import { PredictPopup } from "./PredictPopup";
-import CloseIcon from "@/assets/svg/close-icon.svg";
-
-import { isUndefined } from "@/utils";
-
-import DefaultPredictButton from "./PredictPopup/ActionButtons/DefaultPredictButton";
-import TradeButton from "./PredictPopup/ActionButtons/TradeButton";
-
-import PredictPopup from "./PredictPopup";
-
-const PredictButton: React.FC = () => {
+const PredictButton: React.FC = ({}) => {
const [isOpen, toggleIsOpen] = useToggle(false);
- const [isPopUpOpen, toggleIsPopUpOpen] = useToggle(false);
-
- const { setActiveCardId } = useCardInteraction();
-
- const {
- market,
- isUpPredict,
- differenceBetweenRoutes,
- isLoading: isLoadingComplexRoute,
- hasLiquidity,
- refetchQuotes,
- } = useMarketContext();
-
- const { upToken, downToken, underlyingToken, marketId } = market;
-
- const { data: underlyingBalance } = useBalance(underlyingToken);
- const { data: upBalance } = useBalance(upToken);
- const { data: downBalance } = useBalance(downToken);
-
- const needsSelling = useMemo(
- () =>
- isUpPredict
- ? !isUndefined(downBalance) && downBalance > 0
- : !isUndefined(upBalance) && upBalance > 0,
- [isUpPredict, downBalance, upBalance],
- );
-
- const sellToken = isUpPredict ? downToken : upToken;
- const sellTokenBalance = isUpPredict ? downBalance : upBalance;
- const { data: sellQuote } = useMarketQuote(
- underlyingToken,
- sellToken,
- sellTokenBalance ? formatUnits(sellTokenBalance, 18) : "1",
- );
-
- // if no previous position, carry with the default behaviour
- if (!needsSelling)
- return (
- <>
- {differenceBetweenRoutes > 0 ? (
- {
- setActiveCardId(marketId);
- toggleIsPopUpOpen();
- }}
- />
- ) : (
-
- )}
-
- >
- );
-
- // if previous prediction present, liquidate that to set a new prediction
+ const { hasLiquidity } = useMarketContext();
return (
<>
-
-
- }
- onPress={toggleIsOpen}
- />
-
-
- You have a previous prediction with {isUpPredict ? "DOWN" : "UP"}{" "}
- tokens,
- Sell those tokens to make a new prediction.
-
-
{
- toggleIsOpen();
- refetchQuotes();
- }}
- />
-
-
+
>
);
};
diff --git a/src/app/(homepage)/components/ProjectFunding/PredictPopup/ActionButtons/DefaultPredictButton.tsx b/src/app/(homepage)/components/ProjectFunding/PredictPopup/ActionButtons/DefaultPredictButton.tsx
deleted file mode 100644
index 60ec009..0000000
--- a/src/app/(homepage)/components/ProjectFunding/PredictPopup/ActionButtons/DefaultPredictButton.tsx
+++ /dev/null
@@ -1,131 +0,0 @@
-import { useCallback, useMemo } from "react";
-
-import { Button } from "@kleros/ui-components-library";
-import { sendTransaction, waitForTransactionReceipt } from "@wagmi/core";
-import { useToggle } from "react-use";
-import { useAccount, useConfig } from "wagmi";
-
-import { useWriteErc20Approve } from "@/generated";
-
-import { useMarketContext } from "@/context/MarketContext";
-import { useAllowance } from "@/hooks/useAllowance";
-import { useBalance } from "@/hooks/useBalance";
-
-import { isUndefined } from "@/utils";
-
-import { SWAPR_CONTRACT } from "@/consts";
-
-const DefaultPredictButton: React.FC<{ toggleIsOpen?: () => void }> = ({
- toggleIsOpen,
-}) => {
- const {
- marketQuote,
- marketDownQuote,
- isUpPredict,
- market,
- isLoading,
- hasLiquidity,
- } = useMarketContext();
- const { underlyingToken } = market;
-
- const wagmiConfig = useConfig();
- const { address } = useAccount();
- const [userInteracting, toggleUserInteracting] = useToggle(false);
- const { data: allowance, refetch: refetchAllowance } =
- useAllowance(underlyingToken);
-
- const { data: underlyingBalance, refetch: refetchBalance } =
- useBalance(underlyingToken);
-
- const isAllowance = useMemo(
- () =>
- typeof allowance !== "undefined" &&
- typeof underlyingBalance !== "undefined" &&
- allowance < underlyingBalance,
- [allowance, underlyingBalance],
- );
-
- const { writeContractAsync: increaseAllowance } = useWriteErc20Approve();
-
- const handleAllowance = useCallback(async () => {
- try {
- if (!isUndefined(underlyingBalance)) {
- const hash = await increaseAllowance({
- address: underlyingToken,
- args: [SWAPR_CONTRACT, underlyingBalance],
- });
- await waitForTransactionReceipt(wagmiConfig, {
- hash,
- confirmations: 2,
- });
- refetchAllowance();
- }
- } catch (err) {
- console.log("handleAllowance:", err);
- }
- }, [
- wagmiConfig,
- increaseAllowance,
- refetchAllowance,
- underlyingBalance,
- underlyingToken,
- ]);
-
- const handlePredict = useCallback(async () => {
- try {
- const tx = await (
- isUpPredict ? marketQuote : marketDownQuote
- )?.swapTransaction({
- recipient: address!,
- });
- const hash = await sendTransaction(wagmiConfig, {
- to: tx!.to as `0x${string}`,
- data: tx!.data!.toString() as `0x${string}`,
- value: BigInt(tx?.value?.toString() || 0),
- });
- await waitForTransactionReceipt(wagmiConfig, { hash, confirmations: 2 });
- refetchBalance();
- toggleIsOpen?.();
- } catch (err) {
- console.log("handlePredict:", err);
- }
- }, [
- address,
- marketQuote,
- marketDownQuote,
- wagmiConfig,
- isUpPredict,
- refetchBalance,
- toggleIsOpen,
- ]);
- return (
- {
- toggleUserInteracting(true);
- try {
- if (isAllowance) {
- await handleAllowance();
- } else {
- await handlePredict();
- }
- } finally {
- toggleUserInteracting(false);
- }
- }}
- />
- );
-};
-
-export default DefaultPredictButton;
diff --git a/src/app/(homepage)/components/ProjectFunding/PredictPopup/ActionButtons/SplitButton.tsx b/src/app/(homepage)/components/ProjectFunding/PredictPopup/ActionButtons/SplitButton.tsx
deleted file mode 100644
index ec117d8..0000000
--- a/src/app/(homepage)/components/ProjectFunding/PredictPopup/ActionButtons/SplitButton.tsx
+++ /dev/null
@@ -1,120 +0,0 @@
-import React, { useMemo, useCallback } from "react";
-
-import { Button } from "@kleros/ui-components-library";
-import { waitForTransactionReceipt } from "@wagmi/core";
-import { useToggle } from "react-use";
-import { Address } from "viem";
-import { useConfig, useAccount } from "wagmi";
-
-import {
- useWriteErc20Approve,
- gnosisRouterAddress,
- useWriteGnosisRouterSplitPosition,
- sDaiAddress,
-} from "@/generated";
-
-import { useAllowance } from "@/hooks/useAllowance";
-import { useBalance } from "@/hooks/useBalance";
-
-import { isUndefined } from "@/utils";
-
-interface ISplitButton {
- marketId: `0x${string}`;
- underlyingToken: Address;
- setNextStep: () => void;
-}
-
-const SplitButton: React.FC = ({
- marketId,
- underlyingToken,
- setNextStep,
-}) => {
- const wagmiConfig = useConfig();
- const { address } = useAccount();
- const [isInteracting, toggleIsInteracting] = useToggle(false);
-
- const { data: underlyingBalance } = useBalance(underlyingToken);
-
- const { data: allowance, refetch: refetchAllowance } = useAllowance(
- underlyingToken,
- gnosisRouterAddress,
- );
- const isAllowance = useMemo(
- () =>
- !isUndefined(allowance) &&
- !isUndefined(underlyingBalance) &&
- allowance < underlyingBalance,
- [allowance, underlyingBalance],
- );
-
- const { writeContractAsync: increaseAllowance } = useWriteErc20Approve();
-
- const handleAllowance = useCallback(async () => {
- try {
- if (!isUndefined(underlyingBalance)) {
- const hash = await increaseAllowance({
- address: underlyingToken,
- args: [gnosisRouterAddress, underlyingBalance],
- });
- await waitForTransactionReceipt(wagmiConfig, {
- hash,
- confirmations: 2,
- });
- refetchAllowance();
- }
- } catch (err) {
- console.log("handleAllowance:", err);
- }
- }, [
- wagmiConfig,
- increaseAllowance,
- refetchAllowance,
- underlyingBalance,
- underlyingToken,
- ]);
-
- const { writeContractAsync: splitPosition } =
- useWriteGnosisRouterSplitPosition();
-
- const handleSplit = useCallback(async () => {
- try {
- if (!isUndefined(underlyingBalance)) {
- const hash = await splitPosition({
- args: [sDaiAddress, marketId, underlyingBalance],
- });
- await waitForTransactionReceipt(wagmiConfig, {
- hash,
- confirmations: 2,
- });
- setNextStep();
- }
- } catch (err) {
- console.log("handleSplit:", err);
- }
- }, [splitPosition, marketId, underlyingBalance, setNextStep, wagmiConfig]);
-
- return (
- {
- toggleIsInteracting(true);
- try {
- if (isAllowance) {
- await handleAllowance();
- } else {
- await handleSplit();
- }
- } finally {
- toggleIsInteracting(false);
- }
- }}
- />
- );
-};
-
-export default SplitButton;
diff --git a/src/app/(homepage)/components/ProjectFunding/PredictPopup/ActionButtons/TradeButton.tsx b/src/app/(homepage)/components/ProjectFunding/PredictPopup/ActionButtons/TradeButton.tsx
deleted file mode 100644
index a93ed3b..0000000
--- a/src/app/(homepage)/components/ProjectFunding/PredictPopup/ActionButtons/TradeButton.tsx
+++ /dev/null
@@ -1,121 +0,0 @@
-import { useCallback, useMemo } from "react";
-
-import { Button } from "@kleros/ui-components-library";
-import { SwaprV3Trade } from "@swapr/sdk";
-import { sendTransaction, waitForTransactionReceipt } from "@wagmi/core";
-import { useToggle } from "react-use";
-import { Address, parseUnits } from "viem";
-import { useAccount, useConfig } from "wagmi";
-
-import { useWriteErc20Approve } from "@/generated";
-
-import { useAllowance } from "@/hooks/useAllowance";
-
-import { isUndefined } from "@/utils";
-
-import { SWAPR_CONTRACT } from "@/consts";
-
-interface ITradeButton {
- sellToken: Address;
- quote?: SwaprV3Trade | null;
- setNextStep: () => void;
-}
-
-const TradeButton: React.FC = ({
- sellToken,
- quote,
- setNextStep,
-}) => {
- const { address } = useAccount();
- const wagmiConfig = useConfig();
- const [userInteracting, toggleUserInteracting] = useToggle(false);
-
- const { data: allowance, refetch: refetchAllowance } =
- useAllowance(sellToken);
-
- const amountToTrade = useMemo(
- () =>
- isUndefined(quote)
- ? undefined
- : parseUnits(quote.inputAmount.toExact(), 18),
- [quote],
- );
-
- const isAllowance = useMemo(
- () =>
- !isUndefined(allowance) &&
- !isUndefined(amountToTrade) &&
- allowance < amountToTrade,
- [allowance, amountToTrade],
- );
-
- const { writeContractAsync: increaseAllowance } = useWriteErc20Approve();
-
- const handleAllowance = useCallback(async () => {
- try {
- if (!isUndefined(amountToTrade)) {
- // approve swapr router
- const hash = await increaseAllowance({
- address: sellToken,
- args: [SWAPR_CONTRACT, amountToTrade ?? 0],
- });
- await waitForTransactionReceipt(wagmiConfig, {
- hash,
- confirmations: 2,
- });
- refetchAllowance();
- }
- } catch (err) {
- console.log("handleAllowance:", err);
- }
- }, [
- wagmiConfig,
- increaseAllowance,
- refetchAllowance,
- sellToken,
- amountToTrade,
- ]);
-
- const handleTrade = useCallback(async () => {
- try {
- const tx = await quote?.swapTransaction({
- recipient: address!,
- });
-
- const hash = await sendTransaction(wagmiConfig, {
- to: tx!.to as `0x${string}`,
- data: tx!.data!.toString() as `0x${string}`,
- value: BigInt(tx?.value?.toString() || 0),
- });
- await waitForTransactionReceipt(wagmiConfig, { hash, confirmations: 2 });
- setNextStep();
- } catch (err) {
- console.log("handleTrade:", err);
- }
- }, [address, wagmiConfig, quote, setNextStep]);
-
- return (
- {
- toggleUserInteracting(true);
- try {
- if (isAllowance) {
- await handleAllowance();
- } else {
- await handleTrade();
- }
- } finally {
- toggleUserInteracting(false);
- }
- }}
- />
- );
-};
-
-export default TradeButton;
diff --git a/src/app/(homepage)/components/ProjectFunding/PredictPopup/AmountDisplay.tsx b/src/app/(homepage)/components/ProjectFunding/PredictPopup/AmountDisplay.tsx
new file mode 100644
index 0000000..d6663ab
--- /dev/null
+++ b/src/app/(homepage)/components/ProjectFunding/PredictPopup/AmountDisplay.tsx
@@ -0,0 +1,58 @@
+import { BigNumberField } from "@kleros/ui-components-library";
+import { formatUnits } from "viem";
+
+import InfoIcon from "@/assets/svg/info.svg";
+import MovieIcon from "@/assets/svg/movie.svg";
+
+import { formatValue, isUndefined } from "@/utils";
+
+import { DECIMALS } from "@/consts";
+
+interface IAmountDisplay {
+ value?: bigint;
+ underlyingBalance?: bigint;
+}
+const AmountDisplay: React.FC = ({
+ value,
+ underlyingBalance,
+}) => {
+ return (
+
+
+ You get
+
+
+
+
+
+
+
+
+ {!isUndefined(underlyingBalance) ? (
+
+ {`Available: ${formatValue(underlyingBalance)}`}
+
+ ) : null}
+
+ {!isUndefined(value) ? (
+
+
+
+ {`With ${formatValue(value)} sDAI you get ${formatValue(value)} movie tokens in each movie.`}
+
+
+ ) : null}
+
+ );
+};
+
+export default AmountDisplay;
diff --git a/src/app/(homepage)/components/ProjectFunding/PredictPopup/DefaultPredict.tsx b/src/app/(homepage)/components/ProjectFunding/PredictPopup/DefaultPredict.tsx
deleted file mode 100644
index bfcdaac..0000000
--- a/src/app/(homepage)/components/ProjectFunding/PredictPopup/DefaultPredict.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-import { Card } from "@kleros/ui-components-library";
-import clsx from "clsx";
-
-import { useMarketContext } from "@/context/MarketContext";
-
-import DefaultPredictButton from "./ActionButtons/DefaultPredictButton";
-
-import SkeletonCard from "./SkeletonCard";
-
-import { Route } from ".";
-
-interface IDefaultPredict {
- isSelected: boolean;
- setIsSelected: (val: Route) => void;
- toggleIsOpen: () => void;
-}
-const DefaultPredict: React.FC = ({
- isSelected,
- setIsSelected,
- toggleIsOpen,
-}) => {
- const { isUpPredict, expectedFromDefaultRoute, isLoading } =
- useMarketContext();
-
- const tokenSymbol = isUpPredict ? "UP" : "DOWN";
-
- if (isLoading) {
- return ;
- }
- return (
- setIsSelected(Route.Default)}
- >
-
-
- Default
-
-
- Buy {tokenSymbol} Tokens.
-
-
-
-
-
-
- Expected Return: {expectedFromDefaultRoute?.toFixed(2)}{" "}
- {tokenSymbol} tokens
-
-
-
- 2 steps required
-
-
-
-
-
- );
-};
-export default DefaultPredict;
diff --git a/src/app/(homepage)/components/ProjectFunding/PredictPopup/Header.tsx b/src/app/(homepage)/components/ProjectFunding/PredictPopup/Header.tsx
new file mode 100644
index 0000000..fbbba1d
--- /dev/null
+++ b/src/app/(homepage)/components/ProjectFunding/PredictPopup/Header.tsx
@@ -0,0 +1,57 @@
+import React from "react";
+
+import clsx from "clsx";
+
+import { useMarketContext } from "@/context/MarketContext";
+
+import ArrowDown from "@/assets/svg/long-arrow-down.svg";
+import ArrowUp from "@/assets/svg/long-arrow-up.svg";
+
+const Header: React.FC = () => {
+ const { market, prediction, isUpPredict } = useMarketContext();
+
+ return (
+
+
+ Predict
+
+
+
+
+
+ {market.name}
+
+
+ Score
+
+
+ {prediction}
+
+
+
+
+
+ );
+};
+
+export default Header;
diff --git a/src/app/(homepage)/components/ProjectFunding/PredictPopup/MintSell.tsx b/src/app/(homepage)/components/ProjectFunding/PredictPopup/MintSell.tsx
deleted file mode 100644
index 3832710..0000000
--- a/src/app/(homepage)/components/ProjectFunding/PredictPopup/MintSell.tsx
+++ /dev/null
@@ -1,89 +0,0 @@
-import { Button, Card } from "@kleros/ui-components-library";
-import clsx from "clsx";
-
-import { useMarketContext } from "@/context/MarketContext";
-
-import SkeletonCard from "./SkeletonCard";
-
-import { Route } from ".";
-
-interface IMintSell {
- isSelected: boolean;
- setIsSelected: (val: Route) => void;
- setIsMintScreen: (val: boolean) => void;
-}
-
-const MintSell: React.FC = ({
- isSelected,
- setIsSelected,
- setIsMintScreen,
-}) => {
- const {
- isUpPredict,
- expectedFromMintRoute,
- isLoading,
- percentageIncrease,
- differenceBetweenRoutes,
- } = useMarketContext();
-
- const tokenSymbol = isUpPredict ? "UP" : "DOWN";
-
- if (isLoading) {
- return ;
- }
- return (
- setIsSelected(Route.MintSell)}
- >
-
-
- Mint and Sell
-
-
- Mint UP and DOWN Tokens, Sell {isUpPredict ? "DOWN" : "UP"} tokens,
- and buy more {tokenSymbol} Tokens.
-
-
-
-
-
-
- Expected Return: {expectedFromMintRoute?.toFixed(2)} {tokenSymbol}{" "}
- tokens
-
-
= 0
- ? "text-klerosUIComponentsSuccess"
- : "text-klerosUIComponentsError",
- )}
- >
-
- {differenceBetweenRoutes > 0 && <>⏶>}
- {differenceBetweenRoutes < 0 && <>▼>} {percentageIncrease}
- %
-
-
-
- 6 steps required
-
-
-
setIsMintScreen(true)} />
-
-
- );
-};
-export default MintSell;
diff --git a/src/app/(homepage)/components/ProjectFunding/PredictPopup/MintSellSteps.tsx b/src/app/(homepage)/components/ProjectFunding/PredictPopup/MintSellSteps.tsx
deleted file mode 100644
index cefdde1..0000000
--- a/src/app/(homepage)/components/ProjectFunding/PredictPopup/MintSellSteps.tsx
+++ /dev/null
@@ -1,109 +0,0 @@
-import { useRef, useState } from "react";
-
-import { Button, Steps } from "@kleros/ui-components-library";
-import clsx from "clsx";
-
-import { useMarketContext } from "@/context/MarketContext";
-
-import SplitButton from "./ActionButtons/SplitButton";
-import TradeButton from "./ActionButtons/TradeButton";
-
-enum Step {
- Mint,
- Sell,
- Buy,
-}
-
-interface IMintSellSteps {
- close: () => void;
- toggleIsOpen: () => void;
-}
-const MintSellSteps: React.FC = ({ close, toggleIsOpen }) => {
- const [currentStep, setCurrentStep] = useState(Step.Mint);
- const initialBalanceRef = useRef(null);
- const {
- expectedFromMintRoute,
- isUpPredict,
- market,
- mintReBuyQuote,
- mintSellQuote,
- } = useMarketContext();
- const { marketId, underlyingToken, downToken, upToken } = market;
-
- const steps = [
- {
- title: "Mint",
- subitems: ["Approve in gnosisRouter", "Split position"],
- },
-
- {
- title: "Sell",
- subitems: [
- "Approve token counter in Swapr",
- "Sell token counter to position",
- ],
- },
- {
- title: "Buy",
- },
- ];
- const tokenSymbol = isUpPredict ? "UP" : "DOWN";
- return (
-
-
- Mint, sell, and buy.
-
-
-
- Expected Return: {expectedFromMintRoute?.toFixed(2)} {tokenSymbol}{" "}
- tokens
-
-
-
-
-
- {currentStep === Step.Mint && (
- setCurrentStep(Step.Sell)}
- />
- )}
- {currentStep === Step.Sell && (
- // if user was initially buying upToken, we need to sell downToken,
- // to obtain underlyingToken to further buy more upToken. Vice versa
- setCurrentStep(Step.Buy)}
- />
- )}
- {currentStep === Step.Buy && (
- // buy the upToken/downToken with the underlyingToken acquired from above trade
- {
- toggleIsOpen();
- }}
- />
- )}
-
-
- );
-};
-export default MintSellSteps;
diff --git a/src/app/(homepage)/components/ProjectFunding/PredictPopup/PredictSteps.tsx b/src/app/(homepage)/components/ProjectFunding/PredictPopup/PredictSteps.tsx
new file mode 100644
index 0000000..dce25ab
--- /dev/null
+++ b/src/app/(homepage)/components/ProjectFunding/PredictPopup/PredictSteps.tsx
@@ -0,0 +1,147 @@
+import React, { useMemo } from "react";
+
+import { CustomTimeline } from "@kleros/ui-components-library";
+import clsx from "clsx";
+import { Address } from "viem";
+
+import CheckOutline from "@/assets/svg/check-outline.svg";
+import CircleOutline from "@/assets/svg/circle-outline.svg";
+import CloseOutline from "@/assets/svg/close-circle.svg";
+
+import { formatValue, isUndefined, shortenAddress } from "@/utils";
+
+import Spinner from "./Spinner";
+
+type TimelineItem = React.ComponentProps<
+ typeof CustomTimeline
+>["items"][number];
+
+interface IPredictSteps {
+ tradeExecutor?: Address;
+ toBeAdded: bigint;
+ isCreatingWallet: boolean;
+ isAddingCollateral: boolean;
+ isCollateralAdded: boolean;
+ isProcessingMarkets: boolean;
+ isLoadingQuotes: boolean;
+ isMakingPrediction: boolean;
+ isPredictionSuccessful: boolean;
+ error?: string;
+}
+
+const PredictSteps: React.FC = ({
+ tradeExecutor,
+ toBeAdded,
+ isCreatingWallet,
+ isAddingCollateral,
+ isCollateralAdded,
+ isProcessingMarkets,
+ isLoadingQuotes,
+ isMakingPrediction,
+ isPredictionSuccessful,
+ error,
+}) => {
+ const predictionProgressText = useMemo(() => {
+ if (isMakingPrediction) return "Making prediction...";
+ if (isLoadingQuotes) return "Loading Quotes...";
+ if (isProcessingMarkets) return "Processing markets...";
+ if (isPredictionSuccessful) return "Prediction Successful!";
+ return "";
+ }, [
+ isMakingPrediction,
+ isLoadingQuotes,
+ isProcessingMarkets,
+ isPredictionSuccessful,
+ ]);
+
+ // these will change based on actions
+ const items = useMemo(() => {
+ const steps: TimelineItem[] = [];
+
+ if (tradeExecutor) {
+ steps.push({
+ title: "Trade Wallet",
+ party: "",
+ subtitle: shortenAddress(tradeExecutor),
+ Icon: CheckOutline,
+ state: error ? "disabled" : undefined,
+ });
+ } else {
+ steps.push({
+ title: "Create a Trade Wallet",
+ party: isCreatingWallet ? : "",
+ subtitle: "",
+ state: isCreatingWallet ? "loading" : undefined,
+ Icon: CircleOutline,
+ });
+ }
+
+ steps.push({
+ title: "Add Collateral to wallet",
+ party: isAddingCollateral ? : "",
+ subtitle: `Adding ${formatValue(toBeAdded)} sDAI`,
+ Icon:
+ isCollateralAdded || toBeAdded === 0n ? CheckOutline : CircleOutline,
+ state: isAddingCollateral
+ ? "loading"
+ : error || toBeAdded === 0n
+ ? "disabled"
+ : undefined,
+ });
+
+ steps.push({
+ title: "Make Prediction",
+ party:
+ isMakingPrediction || isLoadingQuotes || isProcessingMarkets ? (
+
+ ) : (
+ ""
+ ),
+ subtitle: predictionProgressText,
+ Icon: isPredictionSuccessful ? CheckOutline : CircleOutline,
+ state:
+ isMakingPrediction || isLoadingQuotes || isProcessingMarkets
+ ? "loading"
+ : error
+ ? "disabled"
+ : undefined,
+ });
+
+ if (!isUndefined(error)) {
+ steps.push({
+ title: "Prediction failed!",
+ subtitle: error,
+ variant: "#ca2314",
+ party: "",
+ Icon: CloseOutline,
+ });
+ }
+ return steps as [TimelineItem, ...TimelineItem[]];
+ }, [
+ tradeExecutor,
+ isCreatingWallet,
+ isAddingCollateral,
+ isCollateralAdded,
+ toBeAdded,
+ isMakingPrediction,
+ isPredictionSuccessful,
+ predictionProgressText,
+ isProcessingMarkets,
+ isLoadingQuotes,
+ error,
+ ]);
+
+ return (
+
+ div:nth-child(2)]:ml-2",
+ "[&_li>div:nth-child(1)>div]:border-l-klerosUIComponentsPrimaryBlue [&_li>div:nth-child(1)>div]:my-2",
+ )}
+ />
+
+ );
+};
+
+export default PredictSteps;
diff --git a/src/app/(homepage)/components/ProjectFunding/PredictPopup/SkeletonCard.tsx b/src/app/(homepage)/components/ProjectFunding/PredictPopup/SkeletonCard.tsx
deleted file mode 100644
index a367400..0000000
--- a/src/app/(homepage)/components/ProjectFunding/PredictPopup/SkeletonCard.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import clsx from "clsx";
-
-import { Skeleton } from "@/components/Skeleton";
-
-const SkeletonCard: React.FC = () => {
- return (
-
- );
-};
-
-export default SkeletonCard;
diff --git a/src/app/(homepage)/components/ProjectFunding/PredictPopup/Spinner.tsx b/src/app/(homepage)/components/ProjectFunding/PredictPopup/Spinner.tsx
new file mode 100644
index 0000000..42bc89c
--- /dev/null
+++ b/src/app/(homepage)/components/ProjectFunding/PredictPopup/Spinner.tsx
@@ -0,0 +1,15 @@
+import React from "react";
+
+import clsx from "clsx";
+
+import SpinnerIcon from "@/assets/svg/spinner.svg";
+const Spinner: React.FC = () => (
+
+);
+
+export default Spinner;
diff --git a/src/app/(homepage)/components/ProjectFunding/PredictPopup/index.tsx b/src/app/(homepage)/components/ProjectFunding/PredictPopup/index.tsx
index e9d9489..7f5814c 100644
--- a/src/app/(homepage)/components/ProjectFunding/PredictPopup/index.tsx
+++ b/src/app/(homepage)/components/ProjectFunding/PredictPopup/index.tsx
@@ -1,71 +1,421 @@
-import { useState } from "react";
+import React, { useEffect, useMemo, useState } from "react";
-import { Modal } from "@kleros/ui-components-library";
+import { Button, Modal } from "@kleros/ui-components-library";
+import { useQueryClient } from "@tanstack/react-query";
+import clsx from "clsx";
+import { Address } from "viem";
+import { useAccount, useBalance } from "wagmi";
-import LightButton from "@/components/LightButton";
+import {
+ useReadSDaiPreviewDeposit,
+ useReadSDaiPreviewRedeem,
+} from "@/generated";
-import CloseIcon from "@/assets/svg/close-icon.svg";
+import { useMarketContext } from "@/context/MarketContext";
+import { useCheckTradeExecutorCreated } from "@/hooks/tradeWallet/useCheckTradeExecutorCreated";
+import { useCreateTradeExecutor } from "@/hooks/tradeWallet/useCreateTradeExecutor";
+import { useDepositToTradeExecutor } from "@/hooks/tradeWallet/useDepositToTradeExecutor";
+import { useTradeExecutorPredict } from "@/hooks/tradeWallet/useTradeExecutorPredict";
+import { fetchTokenBalance, useTokenBalance } from "@/hooks/useTokenBalance";
-import DefaultPredict from "./DefaultPredict";
-import MintSell from "./MintSell";
-import MintSellSteps from "./MintSellSteps";
+import AmountInput, { TokenType } from "@/components/AmountInput";
+
+import ArrowDownIcon from "@/assets/svg/arrow-down.svg";
+
+import { formatValue, isUndefined } from "@/utils";
+import { formatError } from "@/utils/formatError";
+import { getQuotes } from "@/utils/getQuotes";
+import { processMarket } from "@/utils/processMarket";
+
+import { collateral } from "@/consts";
+
+import AmountDisplay from "./AmountDisplay";
+import Header from "./Header";
+import PredictSteps from "./PredictSteps";
-export enum Route {
- Default,
- MintSell,
-}
interface IPredictPopup {
- isOpen?: boolean;
+ isOpen: boolean;
toggleIsOpen: () => void;
}
-const PredictPopup: React.FC = ({ isOpen, toggleIsOpen }) => {
- const [selectedRoute, setSelectedRoute] = useState();
- const [isMinting, setIsMinting] = useState(false);
+
+export const PredictPopup: React.FC = ({
+ isOpen,
+ toggleIsOpen,
+}) => {
+ // states to manage Predict steps, will be refactored ltr rn it's a bit verbose
+ const [createdTradeWallet, setCreatedTradeWallet] = useState();
+ const [isCreatingWallet, setIsCreatingWallet] = useState(false);
+ const [isAddingCollateral, setIsAddingCollateral] = useState(false);
+ const [isCollateralAdded, setIsCollateralAdded] = useState(false);
+ const [isProcessingMarkets, setIsProcessingMarkets] =
+ useState(false);
+ const [isLoadingQuotes, setIsLoadingQuotes] = useState(false);
+ const [isPredictionSuccessful, setIsPredictionSuccessful] =
+ useState(false);
+ const [error, setError] = useState();
+ //==========================
+
+ const [isSending, setIsSending] = useState(false);
+ const [amount, setAmount] = useState();
+ const [selectedToken, setSelectedToken] = useState(TokenType.sDAI);
+
+ const isXDai = selectedToken === TokenType.xDAI;
+
+ const resetStates = () => {
+ setCreatedTradeWallet(undefined);
+ setIsCreatingWallet(false);
+ setIsAddingCollateral(false);
+ setIsCollateralAdded(false);
+ setIsProcessingMarkets(false);
+ setIsLoadingQuotes(false);
+ setIsPredictionSuccessful(false);
+ setAmount(0n);
+ setError(undefined);
+ };
+
+ const { market, predictedPrice } = useMarketContext();
+ const queryClient = useQueryClient();
+
+ // checking to see if user alrd has trade wallet
+ const { address: account } = useAccount();
+ const { data: checkTradeExecutorResult } =
+ useCheckTradeExecutorCreated(account);
+ const tradeExecutor = checkTradeExecutorResult?.predictedAddress;
+
+ // balances
+ const { data: userSDaiBalanceData } = useTokenBalance({
+ address: account,
+ token: collateral.address,
+ });
+ const { data: userXDaiBalanceData } = useBalance({
+ address: account,
+ });
+
+ const { data: walletSDaiBalanceData } = useTokenBalance({
+ address: tradeExecutor,
+ token: collateral.address,
+ });
+ const { data: walletUPBalanceData } = useTokenBalance({
+ address: tradeExecutor,
+ token: market.upToken,
+ });
+
+ const { data: walletDOWNBalanceData } = useTokenBalance({
+ address: tradeExecutor,
+ token: market.downToken,
+ });
+
+ // wallet only holds sDAI, this gives the equivalent amount in xDAI
+ // to inform user how much equivalent xDAI they have
+ const { data: walletXDaiBalance } = useReadSDaiPreviewRedeem({
+ args: [walletSDaiBalanceData?.value ?? 0n],
+ query: {
+ enabled:
+ !isUndefined(walletSDaiBalanceData) &&
+ walletSDaiBalanceData.value > 0 &&
+ isXDai,
+ retry: false,
+ },
+ });
+
+ const { data: walletUnderlyingBalanceData } = useTokenBalance({
+ address: tradeExecutor,
+ token: market.underlyingToken,
+ });
+
+ // tells us the resulting sDAI
+ const { data: resultingDeposit } = useReadSDaiPreviewDeposit({
+ args: [amount ?? 0n],
+ query: {
+ enabled: !isUndefined(amount) && amount > 0,
+ retry: false,
+ },
+ });
+
+ const sDAIDepositAmount = useMemo(() => {
+ if (!isXDai) return amount;
+ return resultingDeposit;
+ }, [resultingDeposit, amount, isXDai]);
+
+ //sDAI required
+ const toBeAdded = useMemo(() => {
+ if (isUndefined(sDAIDepositAmount)) return 0n;
+ return sDAIDepositAmount > (walletSDaiBalanceData?.value ?? 0n)
+ ? sDAIDepositAmount - (walletSDaiBalanceData?.value ?? 0n)
+ : 0n;
+ }, [sDAIDepositAmount, walletSDaiBalanceData]);
+
+ // when using xDAI input, we need to convert the additional sDAI amount required,
+ // back to xDAI to take what's necessary
+ const { data: toBeAddedXDai } = useReadSDaiPreviewRedeem({
+ args: [toBeAdded],
+ query: {
+ enabled: !isUndefined(toBeAdded) && toBeAdded > 0 && isXDai,
+ retry: false,
+ },
+ });
+
+ // can be either xDAI or sDAI
+ const availableBalance = useMemo(() => {
+ return selectedToken === TokenType.sDAI
+ ? (userSDaiBalanceData?.value ?? 0n) +
+ (walletSDaiBalanceData?.value ?? 0n)
+ : (userXDaiBalanceData?.value ?? 0n) + (walletXDaiBalance ?? 0n);
+ }, [
+ selectedToken,
+ userSDaiBalanceData,
+ walletSDaiBalanceData,
+ userXDaiBalanceData,
+ walletXDaiBalance,
+ ]);
+
+ // handle creation
+ const createTradeExecutor = useCreateTradeExecutor();
+
+ // handle deposit
+ const depositToTradeExecutor = useDepositToTradeExecutor(() => {});
+
+ // handle trade (mint movie tokens + predict)
+ const tradeExecutorPredict = useTradeExecutorPredict(() => {
+ setIsPredictionSuccessful(true);
+ setTimeout(() => {
+ toggleIsOpen();
+ resetStates();
+ }, 3000);
+
+ queryClient.refetchQueries({
+ queryKey: ["useTicksData", market.underlyingToken],
+ });
+ });
+
+ // TODO: refactor to separate function
+ const handlePredict = async () => {
+ if (isUndefined(account) || isUndefined(checkTradeExecutorResult)) return;
+
+ const snapshot = {
+ initialSDAIDeposit: sDAIDepositAmount,
+ initialToBeAdded: toBeAdded,
+ initialToBeAddedXDai: toBeAddedXDai,
+ };
+
+ // if trade wallet is empty, or if trade wallet isn't created and no collateral provided to deposit
+ const hasWalletCollateral =
+ checkTradeExecutorResult.isCreated &&
+ !isUndefined(walletUnderlyingBalanceData) &&
+ walletUnderlyingBalanceData?.value > 0n;
+
+ const hasDepositCollateral = (snapshot.initialSDAIDeposit ?? 0n) > 0n;
+
+ const hasPosition =
+ (walletUPBalanceData?.value ?? 0n) > 0n ||
+ (walletDOWNBalanceData?.value ?? 0n) > 0n;
+
+ if (!hasWalletCollateral && !hasDepositCollateral && !hasPosition) {
+ setError("Require collateral to trade");
+ return;
+ }
+ setError(undefined);
+ setIsSending(true);
+ try {
+ let tradeWallet = tradeExecutor;
+ //create wallet, if needed
+ if (!checkTradeExecutorResult.isCreated) {
+ setIsCreatingWallet(true);
+ tradeWallet = (await createTradeExecutor.mutateAsync({ account }))
+ .predictedAddress;
+ if (isUndefined(tradeWallet)) {
+ throw new Error("Failed to create wallet!");
+ }
+ setIsCreatingWallet(false);
+ setCreatedTradeWallet(tradeWallet);
+ } else {
+ setCreatedTradeWallet(tradeExecutor!);
+ }
+
+ console.log("Trade wallet:", tradeWallet);
+
+ // deposit
+ if (
+ !isUndefined(snapshot.initialToBeAdded) &&
+ snapshot.initialToBeAdded > 0n
+ ) {
+ console.log("Depositing amount to wallet:", snapshot.initialToBeAdded);
+ setIsAddingCollateral(true);
+ await depositToTradeExecutor.mutateAsync({
+ token: collateral.address,
+ // toBeAdded is in sDAI, for xDAI deposits we take the value as is
+ amount: isXDai
+ ? (snapshot.initialToBeAddedXDai ?? 0n)
+ : snapshot.initialToBeAdded,
+ tradeExecutor: tradeWallet!,
+ isXDai,
+ });
+
+ if (isXDai) {
+ console.log("Refetching sDai balance.");
+ // it's possible that while converting xDai -> sDai,
+ // the actual amount received is less than the predicted amount
+ const updatedWalletSDaiBalance = await fetchTokenBalance(
+ tradeWallet!,
+ collateral.address,
+ );
+ snapshot.initialSDAIDeposit = updatedWalletSDaiBalance.value;
+ }
+
+ setIsAddingCollateral(false);
+ setIsCollateralAdded(true);
+ }
+
+ // process UP and DOWN markets
+ setIsProcessingMarkets(true);
+ const processedMarkets = await Promise.all([
+ processMarket({
+ underlying: market.underlyingToken,
+ outcome: market.upToken,
+ tradeExecutor: tradeWallet!,
+ mintAmount: snapshot.initialSDAIDeposit,
+ targetPrice: predictedPrice,
+ }),
+ processMarket({
+ underlying: market.underlyingToken,
+ outcome: market.downToken,
+ tradeExecutor: tradeWallet!,
+ mintAmount: snapshot.initialSDAIDeposit,
+ targetPrice: 1 - predictedPrice,
+ }),
+ ]);
+ setIsProcessingMarkets(false);
+ console.log("Processed markets:", processedMarkets);
+
+ // get quotes
+ setIsLoadingQuotes(true);
+ const getQuotesResult = await getQuotes({
+ account: tradeWallet!,
+ processedMarkets,
+ });
+ setIsLoadingQuotes(false);
+ console.log("Quotes result:", getQuotesResult);
+
+ // send the tradeExecution
+ await tradeExecutorPredict.mutateAsync({
+ market,
+ amount: processedMarkets[0].underlyingBalance,
+ tradeExecutor: tradeWallet!,
+ getQuotesResult,
+ mintAmount: snapshot.initialSDAIDeposit,
+ });
+ } catch (e) {
+ if (e instanceof Error) {
+ setError(formatError(e));
+ } else {
+ setError("");
+ }
+ // reset state with if user doesn't make click predict again
+ setTimeout(resetStates, 10000);
+ } finally {
+ setIsSending(false);
+ }
+ };
+
+ useEffect(() => {
+ const error =
+ createTradeExecutor.error ??
+ depositToTradeExecutor.error ??
+ tradeExecutorPredict.error;
+
+ if (error) {
+ setError(formatError(error));
+ }
+ }, [
+ createTradeExecutor.error,
+ depositToTradeExecutor.error,
+ tradeExecutorPredict.error,
+ ]);
+
return (
-
- }
- onPress={toggleIsOpen}
- />
- {isMinting ? (
- setIsMinting(false)}
- {...{ toggleIsOpen }}
- />
- ) : (
-
-
-
- Predict
-
-
- Choose between the default or enhanced option below:
-
-
-
-
+
+
+
+ {/* Amount input */}
+
+
+ You pay
+
+
-
+
+ Trade Wallet:
+ {isXDai
+ ? formatValue(walletXDaiBalance ?? 0n)
+ : formatValue(walletSDaiBalanceData?.value ?? 0n)}
+ {isXDai ? "xDAI" : `sDAI`}
+
+
+ To be added:
+ {isXDai
+ ? formatValue(toBeAddedXDai ?? 0n)
+ : formatValue(toBeAdded)}
+ {isXDai ? "xDAI" : `sDAI`}
+
+ >
+ ) : null}
+
+
+
- )}
+
+
+
+ availableBalance)
+ }
+ isLoading={isSending}
+ />
+
+
);
};
-
-export default PredictPopup;
diff --git a/src/app/(homepage)/components/ProjectFunding/RedeemButton.tsx b/src/app/(homepage)/components/ProjectFunding/RedeemButton.tsx
index ebc5d55..6235d72 100644
--- a/src/app/(homepage)/components/ProjectFunding/RedeemButton.tsx
+++ b/src/app/(homepage)/components/ProjectFunding/RedeemButton.tsx
@@ -1,143 +1,51 @@
-import { useMemo, useState } from "react";
-
import { Button } from "@kleros/ui-components-library";
-import { useQueryClient } from "@tanstack/react-query";
-import { waitForTransactionReceipt } from "@wagmi/core";
-import { useConfig } from "wagmi";
-
-import {
- conditionalRouterAddress,
- sDaiAddress,
- useSimulateConditionalRouterRedeemConditionalToCollateral,
- useWriteConditionalRouterRedeemConditionalToCollateral,
-} from "@/generated";
+import { Address } from "viem";
import { useMarketContext } from "@/context/MarketContext";
-import { useAllowance } from "@/hooks/useAllowance";
-import { useBalance } from "@/hooks/useBalance";
-
-import ApproveButton from "@/components/ApproveButton";
+import { useRedeemToTradeExecutor } from "@/hooks/tradeWallet/useRedeemToTradeExecutor";
+import { useTokenBalance } from "@/hooks/useTokenBalance";
import { isUndefined } from "@/utils";
-import { marketsParentOutcome } from "@/consts/markets";
-
-const RedeemButton: React.FC = () => {
- const wagmiConfig = useConfig();
- const queryClient = useQueryClient();
+interface IRedeemButton {
+ tradeExecutor: Address;
+}
- const [isSending, setIsSending] = useState(false);
+const RedeemButton: React.FC
= ({ tradeExecutor }) => {
const { market } = useMarketContext();
- const { upToken, downToken, marketId } = market;
+ const { upToken, downToken, marketId, parentMarketOutcome } = market;
- const { data: upBalance } = useBalance(upToken);
- const { data: downBalance } = useBalance(downToken);
+ const { data: upBalanceData, isLoading: isLoadingUpBalance } =
+ useTokenBalance({ token: upToken, address: tradeExecutor });
+ const { data: downBalanceData, isLoading: isLoadingDownBalance } =
+ useTokenBalance({ token: downToken, address: tradeExecutor });
- const { data: upAllowance } = useAllowance(upToken, conditionalRouterAddress);
- const { data: downAllowance } = useAllowance(
- downToken,
- conditionalRouterAddress,
- );
-
- // we only redeem one direction, the app works so as the user only has stake in one direction.
- // if a user happens to have both UP and DOWN tokens somehow, from Seer, they can claim twice.
- const { outcomeIndex, amount, approvalConfig } = useMemo(() => {
+ const redeemToTradeExecutor = useRedeemToTradeExecutor();
+ const handleRedeem = async () => {
if (
- isUndefined(upBalance) ||
- isUndefined(downBalance) ||
- isUndefined(upAllowance) ||
- isUndefined(downAllowance)
+ isUndefined(upBalanceData?.value) ||
+ isUndefined(downBalanceData?.value)
)
- return {
- outcomeIndex: undefined,
- amount: undefined,
- approvalConfig: undefined,
- };
-
- if (upBalance > 0) {
- // 1 index is UP
- return {
- outcomeIndex: BigInt(1),
- amount: upBalance,
- approvalConfig:
- upBalance > upAllowance
- ? { token: upToken, name: "UP", amount: upBalance - upAllowance }
- : undefined,
- };
- }
- if (downBalance > 0) {
- // 0 index is DOWN
- return {
- outcomeIndex: BigInt(0),
- amount: downBalance,
- approvalConfig:
- downBalance > downAllowance
- ? {
- token: downToken,
- name: "DOWN",
- amount: downBalance - downAllowance,
- }
- : undefined,
- };
- }
- return {
- outcomeIndex: undefined,
- amount: undefined,
- approvalConfig: undefined,
- };
- }, [upBalance, upAllowance, downBalance, downAllowance, upToken, downToken]);
-
- const {
- data: redeemPositionConfig,
- isLoading,
- isError,
- } = useSimulateConditionalRouterRedeemConditionalToCollateral({
- query: {
- enabled:
- !isUndefined(outcomeIndex) &&
- !isUndefined(amount) &&
- isUndefined(approvalConfig),
- },
- args: [
- sDaiAddress,
+ return;
+
+ redeemToTradeExecutor.mutate({
+ tradeExecutor,
+ tokens: [upToken, downToken],
+ amounts: [upBalanceData.value, downBalanceData.value],
+ outcomeIndexes: [1n, 0n],
+ parentMarketOutcome: BigInt(parentMarketOutcome),
marketId,
- [outcomeIndex ?? 0n],
- [marketsParentOutcome],
- [amount ?? 0n],
- ],
- });
-
- const { writeContractAsync: redeemPosition } =
- useWriteConditionalRouterRedeemConditionalToCollateral();
-
- const handleRedeem = async () => {
- if (isUndefined(redeemPositionConfig)) return;
- setIsSending(true);
-
- const hash = await redeemPosition(redeemPositionConfig.request);
- await waitForTransactionReceipt(wagmiConfig, {
- hash,
- confirmations: 2,
});
- setIsSending(false);
};
- if (upBalance === 0n && downBalance === 0n) return null;
+ if (isLoadingUpBalance || isLoadingDownBalance) return null;
- return approvalConfig ? (
- {
- queryClient.invalidateQueries();
- }}
- />
- ) : (
+ return (
);
};
diff --git a/src/app/(homepage)/components/ProjectFunding/index.tsx b/src/app/(homepage)/components/ProjectFunding/index.tsx
index 759c923..276a8e0 100644
--- a/src/app/(homepage)/components/ProjectFunding/index.tsx
+++ b/src/app/(homepage)/components/ProjectFunding/index.tsx
@@ -5,6 +5,7 @@ import clsx from "clsx";
import { useCardInteraction } from "@/context/CardInteractionContext";
import { useMarketContext } from "@/context/MarketContext";
+import { useTradeWallet } from "@/context/TradeWalletContext";
import { isUndefined } from "@/utils";
@@ -13,7 +14,7 @@ import PositionValue from "./PositionValue";
import PredictButton from "./PredictButton";
import PredictionSlider from "./PredictionSlider";
-const ProjectFunding: React.FC = ({}) => {
+const ProjectFunding: React.FC = () => {
const { setActiveCardId } = useCardInteraction();
const {
isUpPredict,
@@ -32,8 +33,12 @@ const ProjectFunding: React.FC = ({}) => {
details,
marketId,
underlyingToken,
+ minValue,
+ maxValue,
} = market;
+ const { tradeExecutor } = useTradeWallet();
+
return (
{
isDisabled={!hasLiquidity}
aria-label="Prediction"
className="w-auto [&_input]:border-none"
+ minValue={minValue}
+ maxValue={maxValue}
value={
!isUndefined(prediction) ? prediction / precision : undefined
}
@@ -75,13 +82,14 @@ const ProjectFunding: React.FC = ({}) => {
/>
+