Skip to content

Commit b25540b

Browse files
committed
feat: redeem-flow
1 parent b246440 commit b25540b

File tree

9 files changed

+423
-2
lines changed

9 files changed

+423
-2
lines changed

src/abi/ConditionalRouter.ts

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
export const CondtionalRouterAbi = [
2+
{
3+
inputs: [
4+
{
5+
internalType: "contract IConditionalTokens",
6+
name: "_conditionalTokens",
7+
type: "address",
8+
},
9+
{
10+
internalType: "contract IWrapped1155Factory",
11+
name: "_wrapped1155Factory",
12+
type: "address",
13+
},
14+
],
15+
stateMutability: "nonpayable",
16+
type: "constructor",
17+
},
18+
{
19+
inputs: [],
20+
name: "conditionalTokens",
21+
outputs: [
22+
{
23+
internalType: "contract IConditionalTokens",
24+
name: "",
25+
type: "address",
26+
},
27+
],
28+
stateMutability: "view",
29+
type: "function",
30+
},
31+
{
32+
inputs: [
33+
{
34+
internalType: "contract IERC20",
35+
name: "collateralToken",
36+
type: "address",
37+
},
38+
{ internalType: "bytes32", name: "parentCollectionId", type: "bytes32" },
39+
{ internalType: "bytes32", name: "conditionId", type: "bytes32" },
40+
{ internalType: "uint256", name: "indexSet", type: "uint256" },
41+
],
42+
name: "getTokenId",
43+
outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
44+
stateMutability: "view",
45+
type: "function",
46+
},
47+
{
48+
inputs: [{ internalType: "bytes32", name: "conditionId", type: "bytes32" }],
49+
name: "getWinningOutcomes",
50+
outputs: [{ internalType: "bool[]", name: "", type: "bool[]" }],
51+
stateMutability: "view",
52+
type: "function",
53+
},
54+
{
55+
inputs: [
56+
{
57+
internalType: "contract IERC20",
58+
name: "collateralToken",
59+
type: "address",
60+
},
61+
{ internalType: "contract Market", name: "market", type: "address" },
62+
{ internalType: "uint256", name: "amount", type: "uint256" },
63+
],
64+
name: "mergePositions",
65+
outputs: [],
66+
stateMutability: "nonpayable",
67+
type: "function",
68+
},
69+
{
70+
inputs: [
71+
{ internalType: "address", name: "", type: "address" },
72+
{ internalType: "address", name: "", type: "address" },
73+
{ internalType: "uint256[]", name: "", type: "uint256[]" },
74+
{ internalType: "uint256[]", name: "", type: "uint256[]" },
75+
{ internalType: "bytes", name: "", type: "bytes" },
76+
],
77+
name: "onERC1155BatchReceived",
78+
outputs: [{ internalType: "bytes4", name: "", type: "bytes4" }],
79+
stateMutability: "nonpayable",
80+
type: "function",
81+
},
82+
{
83+
inputs: [
84+
{ internalType: "address", name: "", type: "address" },
85+
{ internalType: "address", name: "", type: "address" },
86+
{ internalType: "uint256", name: "", type: "uint256" },
87+
{ internalType: "uint256", name: "", type: "uint256" },
88+
{ internalType: "bytes", name: "", type: "bytes" },
89+
],
90+
name: "onERC1155Received",
91+
outputs: [{ internalType: "bytes4", name: "", type: "bytes4" }],
92+
stateMutability: "nonpayable",
93+
type: "function",
94+
},
95+
{
96+
inputs: [
97+
{
98+
internalType: "contract IERC20",
99+
name: "collateralToken",
100+
type: "address",
101+
},
102+
{ internalType: "contract Market", name: "market", type: "address" },
103+
{ internalType: "uint256[]", name: "outcomeIndexes", type: "uint256[]" },
104+
{
105+
internalType: "uint256[]",
106+
name: "parentOutcomeIndexes",
107+
type: "uint256[]",
108+
},
109+
{ internalType: "uint256[]", name: "amounts", type: "uint256[]" },
110+
],
111+
name: "redeemConditionalToCollateral",
112+
outputs: [],
113+
stateMutability: "nonpayable",
114+
type: "function",
115+
},
116+
{
117+
inputs: [
118+
{
119+
internalType: "contract IERC20",
120+
name: "collateralToken",
121+
type: "address",
122+
},
123+
{ internalType: "contract Market", name: "market", type: "address" },
124+
{ internalType: "uint256[]", name: "outcomeIndexes", type: "uint256[]" },
125+
{ internalType: "uint256[]", name: "amounts", type: "uint256[]" },
126+
],
127+
name: "redeemPositions",
128+
outputs: [],
129+
stateMutability: "nonpayable",
130+
type: "function",
131+
},
132+
{
133+
inputs: [
134+
{
135+
internalType: "contract IERC20",
136+
name: "collateralToken",
137+
type: "address",
138+
},
139+
{ internalType: "contract Market", name: "market", type: "address" },
140+
{ internalType: "uint256", name: "amount", type: "uint256" },
141+
],
142+
name: "splitPosition",
143+
outputs: [],
144+
stateMutability: "nonpayable",
145+
type: "function",
146+
},
147+
{
148+
inputs: [{ internalType: "bytes4", name: "interfaceId", type: "bytes4" }],
149+
name: "supportsInterface",
150+
outputs: [{ internalType: "bool", name: "", type: "bool" }],
151+
stateMutability: "view",
152+
type: "function",
153+
},
154+
{
155+
inputs: [],
156+
name: "wrapped1155Factory",
157+
outputs: [
158+
{
159+
internalType: "contract IWrapped1155Factory",
160+
name: "",
161+
type: "address",
162+
},
163+
],
164+
stateMutability: "view",
165+
type: "function",
166+
},
167+
] as const;
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import { useMemo, useState } from "react";
2+
3+
import { Button } from "@kleros/ui-components-library";
4+
import { useQueryClient } from "@tanstack/react-query";
5+
import { waitForTransactionReceipt } from "@wagmi/core";
6+
import { useConfig } from "wagmi";
7+
8+
import {
9+
conditionalRouterAddress,
10+
sDaiAddress,
11+
useSimulateConditionalRouterRedeemConditionalToCollateral,
12+
useWriteConditionalRouterRedeemConditionalToCollateral,
13+
} from "@/generated";
14+
15+
import { useMarketContext } from "@/context/MarketContext";
16+
import { useAllowance } from "@/hooks/useAllowance";
17+
import { useBalance } from "@/hooks/useBalance";
18+
19+
import ApproveButton from "@/components/ApproveButton";
20+
21+
import { isUndefined } from "@/utils";
22+
23+
import { marketsParentOutcome } from "@/consts/markets";
24+
25+
const RedeemButton: React.FC = () => {
26+
const wagmiConfig = useConfig();
27+
const queryClient = useQueryClient();
28+
29+
const [isSending, setIsSending] = useState(false);
30+
const { market } = useMarketContext();
31+
const { upToken, downToken, marketId } = market;
32+
33+
const { data: upBalance } = useBalance(upToken);
34+
const { data: downBalance } = useBalance(downToken);
35+
36+
const { data: upAllowance } = useAllowance(upToken, conditionalRouterAddress);
37+
const { data: downAllowance } = useAllowance(
38+
downToken,
39+
conditionalRouterAddress,
40+
);
41+
42+
// we only redeem one direction, the app works so as the user only has stake in one direction.
43+
// if a user happens to have both UP and DOWN tokens somehow, from Seer, they can claim twice.
44+
const { outcomeIndex, amount, approvalConfig } = useMemo(() => {
45+
if (
46+
isUndefined(upBalance) ||
47+
isUndefined(downBalance) ||
48+
isUndefined(upAllowance) ||
49+
isUndefined(downAllowance)
50+
)
51+
return {
52+
outcomeIndex: undefined,
53+
amount: undefined,
54+
approvalConfig: undefined,
55+
};
56+
57+
if (upBalance > 0) {
58+
// 1 index is UP
59+
return {
60+
outcomeIndex: BigInt(1),
61+
amount: upBalance,
62+
approvalConfig:
63+
upBalance > upAllowance
64+
? { token: upToken, name: "UP", amount: upBalance - upAllowance }
65+
: undefined,
66+
};
67+
}
68+
if (downBalance > 0) {
69+
// 0 index is DOWN
70+
return {
71+
outcomeIndex: BigInt(0),
72+
amount: downBalance,
73+
approvalConfig:
74+
downBalance > downAllowance
75+
? {
76+
token: downToken,
77+
name: "DOWN",
78+
amount: downBalance - downAllowance,
79+
}
80+
: undefined,
81+
};
82+
}
83+
return {
84+
outcomeIndex: undefined,
85+
amount: undefined,
86+
approvalConfig: undefined,
87+
};
88+
}, [upBalance, upAllowance, downBalance, downAllowance, upToken, downToken]);
89+
90+
const {
91+
data: redeemPositionConfig,
92+
isLoading,
93+
isError,
94+
} = useSimulateConditionalRouterRedeemConditionalToCollateral({
95+
query: {
96+
enabled:
97+
!isUndefined(outcomeIndex) &&
98+
!isUndefined(amount) &&
99+
isUndefined(approvalConfig),
100+
},
101+
args: [
102+
sDaiAddress,
103+
marketId,
104+
[outcomeIndex ?? 0n],
105+
[marketsParentOutcome],
106+
[amount ?? 0n],
107+
],
108+
});
109+
110+
const { writeContractAsync: redeemPosition } =
111+
useWriteConditionalRouterRedeemConditionalToCollateral();
112+
113+
const handleRedeem = async () => {
114+
if (isUndefined(redeemPositionConfig)) return;
115+
setIsSending(true);
116+
117+
const hash = await redeemPosition(redeemPositionConfig.request);
118+
await waitForTransactionReceipt(wagmiConfig, {
119+
hash,
120+
confirmations: 2,
121+
});
122+
setIsSending(false);
123+
};
124+
125+
if (upBalance === 0n && downBalance === 0n) return null;
126+
127+
return approvalConfig ? (
128+
<ApproveButton
129+
{...approvalConfig}
130+
spender={conditionalRouterAddress}
131+
callback={() => {
132+
queryClient.invalidateQueries();
133+
}}
134+
/>
135+
) : (
136+
<Button
137+
text="Claim"
138+
onPress={handleRedeem}
139+
isLoading={isLoading || isSending}
140+
isDisabled={isLoading || isError || isSending}
141+
/>
142+
);
143+
};
144+
145+
export default RedeemButton;

src/app/(homepage)/components/ProjectFunding/index.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import Details from "./Details";
1212
import PositionValue from "./PositionValue";
1313
import PredictButton from "./PredictButton";
1414
import PredictionSlider from "./PredictionSlider";
15+
import RedeemButton from "./RedeemButton";
1516

1617
const ProjectFunding: React.FC = ({}) => {
1718
const { setActiveCardId } = useCardInteraction();
@@ -22,6 +23,7 @@ const ProjectFunding: React.FC = ({}) => {
2223
setPrediction,
2324
showEstimateVariant,
2425
hasLiquidity,
26+
isResolved,
2527
} = useMarketContext();
2628
const {
2729
name,
@@ -92,9 +94,10 @@ const ProjectFunding: React.FC = ({}) => {
9294
</div>
9395
</div>
9496
<div className="flex w-full flex-col">
95-
<div className="flex gap-2">
97+
<div className="flex w-full items-center justify-between gap-2">
9698
<PositionValue {...{ upToken, downToken, underlyingToken }} />
9799
{/* <OpenOrders /> */}
100+
{isResolved ? <RedeemButton /> : null}
98101
</div>
99102
<Accordion
100103
aria-label="accordion"

0 commit comments

Comments
 (0)