Skip to content

Commit 47e308d

Browse files
authored
Merge pull request #116 from MeshJS/staking-tab
Staking tab
2 parents 08afb38 + ba0aa8d commit 47e308d

File tree

8 files changed

+581
-33
lines changed

8 files changed

+581
-33
lines changed

src/components/common/overall-layout/menus/multisig-wallet.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
1-
import { Banknote, Info, List, Signature, UserRoundPen, Landmark } from "lucide-react";
1+
import { Banknote, Info, List, Landmark, UserRoundPen, ChartNoAxesColumnIncreasing } from "lucide-react";
22
import { useRouter } from "next/router";
33
import MenuLink from "./menu-link";
44
import usePendingTransactions from "@/hooks/usePendingTransactions";
55
import useUserWallets from "@/hooks/useUserWallets";
66
import { Badge } from "@/components/ui/badge";
77
import { ChatBubbleIcon } from "@radix-ui/react-icons";
88
import usePendingSignables from "@/hooks/usePendingSignables";
9+
import useMultisigWallet from "@/hooks/useMultisigWallet";
910

1011
export default function MenuWallet() {
1112
const router = useRouter();
1213
const baseUrl = `/wallets/${router.query.wallet as string | undefined}/`;
1314
const { wallets } = useUserWallets();
1415
const { transactions } = usePendingTransactions();
1516
const { signables } = usePendingSignables();
17+
const { multisigWallet } = useMultisigWallet();
1618
if (!wallets) return;
1719
return (
1820
<nav className="grid h-full items-start px-2 text-sm font-medium lg:px-4">
@@ -60,6 +62,15 @@ export default function MenuWallet() {
6062
)}
6163
</div>
6264
</MenuLink>
65+
{multisigWallet && multisigWallet.stakingEnabled() && <MenuLink
66+
href={`${baseUrl}staking`}
67+
className={
68+
router.pathname == "/wallets/[wallet]/staking" ? "text-white" : ""
69+
}
70+
>
71+
<ChartNoAxesColumnIncreasing className="h-6 w-6" />
72+
Staking
73+
</MenuLink>}
6374
<MenuLink
6475
href={`${baseUrl}assets`}
6576
className={

src/components/pages/wallet/governance/proposal/voteButtton.tsx

Lines changed: 51 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
SelectValue,
1818
} from "@/components/ui/select";
1919
import { ToastAction } from "@/components/ui/toast";
20+
import useMultisigWallet from "@/hooks/useMultisigWallet";
2021

2122
interface VoteButtonProps {
2223
appWallet: Wallet;
@@ -38,7 +39,7 @@ export default function VoteButton({
3839
const setAlert = useSiteStore((state) => state.setAlert);
3940
const network = useSiteStore((state) => state.network);
4041
const { newTransaction } = useTransaction();
41-
42+
const { multisigWallet } = useMultisigWallet();
4243
async function vote() {
4344
if (drepInfo === undefined) {
4445
setAlert("DRep not found");
@@ -60,11 +61,14 @@ export default function VoteButton({
6061
setLoading(false);
6162
return;
6263
}
63-
64+
if (!multisigWallet)
65+
throw new Error("Multisig Wallet could not be built.");
6466
const dRepId = appWallet.dRepId;
6567
const txBuilder = getTxBuilder(network);
6668
const blockchainProvider = getProvider(network);
67-
const utxos = await blockchainProvider.fetchAddressUTxOs(appWallet.address);
69+
const utxos = await blockchainProvider.fetchAddressUTxOs(
70+
appWallet.address,
71+
);
6872

6973
const assetMap = new Map<Unit, Quantity>();
7074
assetMap.set("lovelace", "5000000");
@@ -76,11 +80,10 @@ export default function VoteButton({
7680
utxo.input.txHash,
7781
utxo.input.outputIndex,
7882
utxo.output.amount,
79-
utxo.output.address
83+
utxo.output.address,
8084
)
8185
.txInScript(appWallet.scriptCbor);
8286
}
83-
console.log(certIndex)
8487
txBuilder
8588
.vote(
8689
{
@@ -93,12 +96,23 @@ export default function VoteButton({
9396
},
9497
{
9598
voteKind: voteKind,
96-
}
99+
},
97100
)
98101
.voteScript(appWallet.scriptCbor)
99102
.selectUtxosFrom(utxos)
100103
.changeAddress(appWallet.address);
104+
105+
const paymentKeys = multisigWallet.getKeysByRole(0) ?? [];
106+
for (const key of paymentKeys) {
107+
txBuilder.requiredSignerHash(key.keyHash);
108+
}
101109

110+
if (multisigWallet.stakingEnabled()) {
111+
const stakingKeys = multisigWallet.getKeysByRole(2) ?? [];
112+
for (const key of stakingKeys) {
113+
txBuilder.requiredSignerHash(key.keyHash);
114+
}
115+
}
102116
await newTransaction({
103117
txBuilder,
104118
description: `Vote: ${voteKind} - ${description}`,
@@ -113,7 +127,10 @@ export default function VoteButton({
113127

114128
setAlert("Vote transaction successfully created!");
115129
} catch (error) {
116-
if (error instanceof Error && error.message.includes("User rejected transaction")) {
130+
if (
131+
error instanceof Error &&
132+
error.message.includes("User rejected transaction")
133+
) {
117134
toast({
118135
title: "Transaction Aborted",
119136
description: "You canceled the vote transaction.",
@@ -149,30 +166,32 @@ export default function VoteButton({
149166
}
150167

151168
return (
152-
<div className="flex flex-col items-center justify-center w-full max-w-sm space-y-2">
153-
<Select
154-
value={voteKind}
155-
onValueChange={(value) => setVoteKind(value as "Yes" | "No" | "Abstain")}
156-
>
157-
<SelectTrigger className="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500">
158-
<SelectValue placeholder="Select Vote Kind" />
159-
</SelectTrigger>
160-
<SelectContent>
161-
<SelectGroup>
162-
<SelectItem value="Yes">Yes</SelectItem>
163-
<SelectItem value="No">No</SelectItem>
164-
<SelectItem value="Abstain">Abstain</SelectItem>
165-
</SelectGroup>
166-
</SelectContent>
167-
</Select>
169+
<div className="flex w-full max-w-sm flex-col items-center justify-center space-y-2">
170+
<Select
171+
value={voteKind}
172+
onValueChange={(value) =>
173+
setVoteKind(value as "Yes" | "No" | "Abstain")
174+
}
175+
>
176+
<SelectTrigger className="w-full rounded-md border border-gray-300 px-4 py-2 focus:ring-2 focus:ring-blue-500">
177+
<SelectValue placeholder="Select Vote Kind" />
178+
</SelectTrigger>
179+
<SelectContent>
180+
<SelectGroup>
181+
<SelectItem value="Yes">Yes</SelectItem>
182+
<SelectItem value="No">No</SelectItem>
183+
<SelectItem value="Abstain">Abstain</SelectItem>
184+
</SelectGroup>
185+
</SelectContent>
186+
</Select>
168187

169-
<Button
170-
onClick={vote}
171-
disabled={loading || proposalId.length !== 66}
172-
className="w-full px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white font-semibold rounded-md shadow"
173-
>
174-
{loading ? "Voting..." : "Vote"}
175-
</Button>
176-
</div>
188+
<Button
189+
onClick={vote}
190+
disabled={loading || proposalId.length !== 66}
191+
className="w-full rounded-md bg-blue-600 px-6 py-2 font-semibold text-white shadow hover:bg-blue-700"
192+
>
193+
{loading ? "Voting..." : "Vote"}
194+
</Button>
195+
</div>
177196
);
178-
}
197+
}
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import { useState } from "react";
2+
import { Button } from "@/components/ui/button";
3+
import { Loader } from "lucide-react";
4+
import { StakingInfo } from "../stakingInfoCard";
5+
import { Wallet } from "@/types/wallet";
6+
import { deserializePoolId, UTxO } from "@meshsdk/core";
7+
import { MultisigWallet } from "@/utils/multisigSDK";
8+
import { ToastAction } from "@radix-ui/react-toast";
9+
import { toast } from "@/hooks/use-toast";
10+
import { getTxBuilder } from "@/utils/get-tx-builder";
11+
import { getProvider } from "@/utils/get-provider";
12+
import useTransaction from "@/hooks/useTransaction";
13+
export default function StakeButton({
14+
stakingInfo,
15+
appWallet,
16+
mWallet,
17+
utxos,
18+
network,
19+
poolHex,
20+
action,
21+
}: {
22+
stakingInfo: StakingInfo;
23+
appWallet: Wallet;
24+
mWallet: MultisigWallet;
25+
utxos: UTxO[];
26+
network: number;
27+
poolHex: string;
28+
action: "register" | "deregister" | "delegate" | "withdrawal" | "registerAndDelegate";
29+
}) {
30+
const { newTransaction } = useTransaction();
31+
const [loading, setLoading] = useState(false);
32+
33+
async function Stake() {
34+
setLoading(true);
35+
try {
36+
if (!mWallet) throw new Error("Multisig Wallet could not be built.");
37+
38+
const rewardAddress = mWallet.getStakeAddress();
39+
if (!rewardAddress) throw new Error("Reward Address could not be built.");
40+
41+
const stakingScript = mWallet.getStakingScript();
42+
if (!stakingScript) throw new Error("Staking Script could not be built.");
43+
44+
const txBuilder = getTxBuilder(network);
45+
const selectedUtxos = utxos;
46+
47+
for (const utxo of selectedUtxos) {
48+
txBuilder
49+
.txIn(
50+
utxo.input.txHash,
51+
utxo.input.outputIndex,
52+
utxo.output.amount,
53+
utxo.output.address,
54+
)
55+
.txInScript(appWallet.scriptCbor);
56+
}
57+
58+
const actionsMap = {
59+
register: {
60+
execute: () => txBuilder.registerStakeCertificate(rewardAddress),
61+
description: "Register stake.",
62+
successTitle: "Stake Registered",
63+
successMessage: "Your stake address has been registered.",
64+
},
65+
deregister: {
66+
execute: () => txBuilder.deregisterStakeCertificate(rewardAddress),
67+
description: "Deregister stake.",
68+
successTitle: "Stake Deregistered",
69+
successMessage: "Your stake address has been deregistered.",
70+
},
71+
delegate: {
72+
execute: () => txBuilder.delegateStakeCertificate(rewardAddress, poolHex),
73+
description: "Delegate stake.",
74+
successTitle: "Stake Delegated",
75+
successMessage: "Your stake has been delegated.",
76+
},
77+
withdrawal: {
78+
execute: () => txBuilder.withdrawal(rewardAddress, stakingInfo.rewards),
79+
description: "Withdraw rewards.",
80+
successTitle: "Rewards Withdrawn",
81+
successMessage: "Your staking rewards have been withdrawn.",
82+
},
83+
registerAndDelegate: {
84+
execute: () => {
85+
txBuilder.registerStakeCertificate(rewardAddress);
86+
txBuilder.delegateStakeCertificate(rewardAddress, poolHex);
87+
},
88+
description: "Register & delegate stake.",
89+
successTitle: "Stake Registered & Delegated",
90+
successMessage: "Your stake address has been registered and delegated.",
91+
},
92+
};
93+
94+
const actionConfig = actionsMap[action];
95+
if (!actionConfig) {
96+
throw new Error("Invalid staking action.");
97+
}
98+
99+
actionConfig.execute();
100+
101+
txBuilder
102+
.selectUtxosFrom(utxos)
103+
.changeAddress(appWallet.address)
104+
.certificateScript(stakingScript);
105+
106+
await newTransaction({
107+
txBuilder,
108+
description: actionConfig.description,
109+
});
110+
111+
toast({
112+
title: actionConfig.successTitle,
113+
description: actionConfig.successMessage,
114+
duration: 5000,
115+
});
116+
} catch (error) {
117+
if (
118+
error instanceof Error &&
119+
error.message.includes("User rejected transaction")
120+
) {
121+
toast({
122+
title: "Transaction Aborted",
123+
description: "You canceled the transaction.",
124+
duration: 5000,
125+
});
126+
} else {
127+
toast({
128+
title: "Transaction Failed",
129+
description: `Error: ${error}`,
130+
duration: 10000,
131+
action: (
132+
<ToastAction
133+
altText="Copy error"
134+
onClick={() => {
135+
navigator.clipboard.writeText(JSON.stringify(error));
136+
toast({
137+
title: "Error Copied",
138+
description: "Error details copied to clipboard.",
139+
duration: 5000,
140+
});
141+
}}
142+
>
143+
Copy Error
144+
</ToastAction>
145+
),
146+
variant: "destructive",
147+
});
148+
console.error("Transaction error:", error);
149+
}
150+
} finally {
151+
setLoading(false);
152+
}
153+
}
154+
155+
return (
156+
<Button variant="outline" onClick={Stake} disabled={loading}>
157+
{loading ? <Loader className="mr-2 h-4 w-4 animate-spin" /> : null}
158+
{action.charAt(0).toUpperCase() + action.slice(1)}
159+
</Button>
160+
);
161+
}

0 commit comments

Comments
 (0)