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
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -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=
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"plugin:prettier/recommended",
"prettier"
],
"ignorePatterns": ["**/**/generated.ts"],
"ignorePatterns": ["**/generated.ts", "src/hooks/liquidity/gql/*"],
"rules": {
"max-len": [
"warn",
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,6 @@ next-env.d.ts

# wagmi generated
/src/generated.ts

# gql
/src/hooks/liquidity/gql
29 changes: 29 additions & 0 deletions codegen.ts
Original file line number Diff line number Diff line change
@@ -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;
13 changes: 11 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion src/app/(homepage)/components/AdvancedSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const AdvancedSection: React.FC = () => {
rel="noreferrer noopener"
className="text-klerosUIComponentsPrimaryBlue items-center text-sm"
>
Check it out <ExternalArrow className="mml-2 inline size-4" />
Check it out <ExternalArrow className="ml-2 inline size-4" />
</Link>
</p>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,47 @@
import { BigNumberField, DropdownSelect } from "@kleros/ui-components-library";
import { useMemo } from "react";

import { BigNumberField } from "@kleros/ui-components-library";
import clsx from "clsx";
import { parseUnits, formatUnits } from "viem";

import DAIIcon from "@/assets/svg/dai.svg";
import LightButton from "@/components/LightButton";

import { formatValue, isUndefined } from "@/utils";

export enum TokenType {
sDAI,
xDAI,
}
interface IAmountInput {
setAmount: (amount: bigint) => void;
setSelectedToken: (token: TokenType) => void;
notEnoughBalance: boolean;
defaultValue?: bigint;
value?: bigint;
balance?: bigint;
isMerge?: boolean;
}

const AmountInput: React.FC<IAmountInput> = ({
setAmount,
setSelectedToken,
notEnoughBalance,
defaultValue,
value,
balance,
isMerge = false,
}) => {
const notEnoughBalance = useMemo(() => {
if (isMerge) return false;
if (!isUndefined(value) && !isUndefined(balance) && value > balance)
return true;
return false;
}, [value, balance, isMerge]);

const handleMaxClick = () => {
if (!isUndefined(balance)) {
setAmount(balance);
}
};

return (
<div className="relative mb-4">
<div className="relative mb-8">
<div className="border-klerosUIComponentsStroke rounded-base flex h-fit flex-row border">
<BigNumberField
className={clsx(
"inline-block w-36",
"inline-block w-96",
"[&_input]:rounded-r-none [&_input]:border-none [&_input]:focus:shadow-none",
)}
onChange={(e) => {
Expand All @@ -44,37 +57,30 @@ const AmountInput: React.FC<IAmountInput> = ({
value={
typeof value !== "undefined" ? formatUnits(value, 18) : undefined
}
isDisabled={typeof value !== "undefined"}
/>
<DropdownSelect
className={clsx(
"[&>button]:bg-klerosUIComponentsMediumBlue [&>button]:h-11.25 [&>button]:w-fit",
"[&>button]:border-none [&>button]:focus:shadow-none",
"[&>button]:rounded-l-none",
)}
callback={(item) => {
setSelectedToken(item.itemValue);
}}
defaultSelectedKey={TokenType.sDAI}
items={[
{
text: "sDAI",
itemValue: TokenType.sDAI,
id: TokenType.sDAI,
icon: <DAIIcon className="mr-2 size-6" />,
},
{
text: "xDAI",
itemValue: TokenType.xDAI,
id: TokenType.xDAI,
icon: <DAIIcon className="mr-2 size-6" />,
},
]}
isDisabled={isMerge}
/>
</div>
<span className="text-light-mode-red-2 absolute text-sm">
{notEnoughBalance ? "Not enough balance." : undefined}
</span>
{!notEnoughBalance && (
<span className="text-klerosUIComponentsSecondaryText absolute pt-1 text-sm">
{!isUndefined(balance)
? `Available: ${formatValue(balance)}`
: "Loading..."}
</span>
)}
{isMerge ? null : (
<LightButton
small
text="Max"
onPress={handleMaxClick}
className={clsx(
"absolute -right-1 mt-1 px-1 py-0.5",
"[&_.button-text]:text-klerosUIComponentsSecondaryText [&_.button-text]:text-sm",
)}
/>
)}
</div>
);
};
Expand Down
150 changes: 15 additions & 135 deletions src/app/(homepage)/components/ParticipateSection/Mint/MergeButton.tsx
Original file line number Diff line number Diff line change
@@ -1,151 +1,31 @@
import React, { useMemo } from "react";
import React 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 { Address } from "viem";

import {
gnosisRouterAddress,
gnosisRouterAbi,
sDaiAddress,
useWriteErc20Approve,
useWriteGnosisRouterMergePositions,
} from "@/generated";

import { useTokenAllowances } from "@/hooks/useTokenAllowances";

import { parentMarket, invalidMarket, markets } from "@/consts/markets";
import { useTradeExecutorMerge } from "@/hooks/tradeWallet/useTradeExecutorMerge";

interface IMergeButton {
amount: bigint;
isMinting: boolean;
toggleIsMinting: (value: boolean) => void;
refetchSDai: () => void;
refetchBalances: () => void;
tradeExecutor: Address;
}

const MergeButton: React.FC<IMergeButton> = ({
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 MergeButton: React.FC<IMergeButton> = ({ amount, tradeExecutor }) => {
const tradeExecutorMerge = useTradeExecutorMerge();

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],
}),
const handleSubmit = () => {
tradeExecutorMerge.mutate({
tradeExecutor,
amount,
});
return calls;
}, [amount, needApproval]);

const { writeContractAsync: approve } = useWriteErc20Approve();
const { writeContractAsync: mergePositions } =
useWriteGnosisRouterMergePositions();

};
return (
<Button
isLoading={isMinting}
isDisabled={isMinting}
isLoading={tradeExecutorMerge.isPending}
isDisabled={tradeExecutorMerge.isPending || amount === 0n}
className="absolute right-1/2 bottom-0 translate-1/2"
text={
atomicSupport || needApproval.length === 0
? "Merge to sDAI"
: `Approve ${markets.length + 2 - needApproval.length}/${markets.length + 1}`
}
onPress={async () => {
toggleIsMinting(true);
if (atomicSupport && typeof calls !== "undefined") {
sendCalls(
{ calls },
{
onSettled: () => {
refetchSDai();
toggleIsMinting(false);
},
},
);
} else if (needApproval.length > 0) {
try {
const hash = await approve({
address: needApproval[0],
args: [gnosisRouterAddress, amount],
});
await waitForTransactionReceipt(wagmiConfig, {
hash,
confirmations: 2,
});
allowances.refetch();
} finally {
toggleIsMinting(false);
}
} else {
try {
const hash = await mergePositions({
args: [sDaiAddress, parentMarket, amount],
});
await waitForTransactionReceipt(wagmiConfig, {
hash,
confirmations: 2,
});
} finally {
refetchSDai();
refetchBalances();
toggleIsMinting(false);
}
}
}}
text="Merge to sDAI"
onPress={handleSubmit}
/>
);
};
Expand Down
Loading