Skip to content

Commit c121e29

Browse files
committed
feat: update user model and wallet schema to include DRep key
- Updated `package.json` to use the latest version of `@meshsdk/react`. - Modified `User` model in Prisma schema to add `drepKeyHash` field. - Updated `Wallet` model in Prisma schema to include `signersDRepKeys` array. - Enhanced `RootLayout` component to handle DRep key retrieval and user updates. - Added logic in `EditSigners` component to manage DRep keys for signers. - Implemented `updateUser` mutation in user router to allow updating of DRep key. - Updated wallet router to handle DRep keys in signers' data. - Created migration script to add new columns for DRep keys in the database.
1 parent c81f3ff commit c121e29

File tree

8 files changed

+1394
-218
lines changed

8 files changed

+1394
-218
lines changed

package-lock.json

Lines changed: 1221 additions & 189 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"@meshsdk/core": "^1.9.0-beta.18",
2424
"@meshsdk/core-csl": "^1.9.0-beta.18",
2525
"@meshsdk/core-cst": "^1.9.0-beta.19",
26-
"@meshsdk/react": "^1.9.0-beta.18",
26+
"@meshsdk/react": "^1.9.0-beta.69",
2727
"@octokit/core": "^6.1.2",
2828
"@prisma/client": "^6.4.1",
2929
"@radix-ui/react-accordion": "^1.2.0",
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-- AlterTable
2+
ALTER TABLE "User" ADD COLUMN "drepKeyHash" TEXT NOT NULL DEFAULT '';
3+
4+
-- AlterTable
5+
ALTER TABLE "Wallet" ADD COLUMN "signersDRepKeys" TEXT[];

prisma/schema.prisma

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ model User {
1414
id String @id @default(cuid())
1515
address String @unique
1616
stakeAddress String @unique
17+
drepKeyHash String @default("")
1718
nostrKey String @unique
1819
discordId String @default("")
1920
}
@@ -24,6 +25,7 @@ model Wallet {
2425
description String?
2526
signersAddresses String[]
2627
signersStakeKeys String[]
28+
signersDRepKeys String[]
2729
signersDescriptions String[]
2830
numRequiredSigners Int?
2931
verified String[]

src/components/common/overall-layout/layout.tsx

Lines changed: 66 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import useUser from "@/hooks/useUser";
99
import { useUserStore } from "@/lib/zustand/user";
1010
import useAppWallet from "@/hooks/useAppWallet";
1111

12-
import SessionProvider from "@/components/SessionProvider"
12+
import SessionProvider from "@/components/SessionProvider";
1313
import { getServerSession } from "next-auth";
1414

1515
import MenuWallets from "@/components/common/overall-layout/menus/wallets";
@@ -52,6 +52,9 @@ export default function RootLayout({
5252
const { mutate: createUser } = api.user.createUser.useMutation({
5353
onError: (e) => console.error(e),
5454
});
55+
const { mutate: updateUser } = api.user.updateUser.useMutation({
56+
onError: (e) => console.error(e),
57+
});
5558

5659
// Single effect for address + user creation
5760
useEffect(() => {
@@ -63,30 +66,51 @@ export default function RootLayout({
6366
if (!address) address = (await wallet.getUnusedAddresses())[0];
6467
if (address) setUserAddress(address);
6568

66-
// 2) If user doesn't exist, create it
69+
// 2) Get stake address
70+
const stakeAddress = (await wallet.getRewardAddresses())[0];
71+
if (!stakeAddress || !address) {
72+
console.error("No stake address or payment address found");
73+
return;
74+
}
75+
76+
// 3) Get DRep key hash
77+
const dRepKey = await wallet.getDRep();
78+
if (!dRepKey) {
79+
console.error("No DRep key found");
80+
return;
81+
}
82+
const drepKeyHash = dRepKey.publicKeyHash;
83+
if (!drepKeyHash) {
84+
console.error("No DRep key hash found:", drepKeyHash);
85+
return;
86+
}
87+
88+
// 4) If user doesn't exist create it
6789
if (!isLoading && user === null) {
68-
const stakeAddress = (await wallet.getRewardAddresses())[0];
69-
if (!stakeAddress || !address) {
70-
console.error("No stake address or payment address found");
71-
return;
72-
}
7390
const nostrKey = generateNsec();
7491
createUser({
7592
address,
7693
stakeAddress,
94+
drepKeyHash,
7795
nostrKey: JSON.stringify(nostrKey),
7896
});
7997
}
98+
99+
// 5) If user exists but missing fields, update it
100+
if (
101+
!isLoading &&
102+
user &&
103+
user !== null &&
104+
(user.stakeAddress !== stakeAddress || user.drepKeyHash !== drepKeyHash)
105+
) {
106+
updateUser({
107+
address,
108+
stakeAddress,
109+
drepKeyHash,
110+
});
111+
}
80112
})();
81-
}, [
82-
connected,
83-
wallet,
84-
user,
85-
isLoading,
86-
createUser,
87-
generateNsec,
88-
setUserAddress,
89-
]);
113+
}, [connected, wallet, user, isLoading, generateNsec, setUserAddress]);
90114

91115
const isWalletPath = router.pathname.includes("/wallets/[wallet]");
92116
const walletPageRoute = router.pathname.split("/wallets/[wallet]/")[1];
@@ -99,12 +123,18 @@ export default function RootLayout({
99123
{isLoading && <Loading />}
100124

101125
{/* Sidebar for larger screens */}
102-
<aside className="hidden border-r border-gray-200/30 dark:border-white/[0.03] bg-muted/40 md:block">
126+
<aside className="hidden border-r border-gray-200/30 bg-muted/40 dark:border-white/[0.03] md:block">
103127
<div className="flex h-full max-h-screen flex-col">
104-
<header className="flex h-14 items-center border-b border-gray-200/30 dark:border-white/[0.03] px-4 lg:h-16 lg:px-6" id="logo-header" data-header="sidebar">
128+
<header
129+
className="flex h-14 items-center border-b border-gray-200/30 px-4 dark:border-white/[0.03] lg:h-16 lg:px-6"
130+
id="logo-header"
131+
data-header="sidebar"
132+
>
105133
<Link href="/" className="flex items-center gap-3">
106134
<Logo />
107-
<span className="font-medium text-sm md:text-base lg:text-lg tracking-[-0.01em] select-none">Multi-Sig Platform</span>
135+
<span className="select-none text-sm font-medium tracking-[-0.01em] md:text-base lg:text-lg">
136+
Multi-Sig Platform
137+
</span>
108138
</Link>
109139
</header>
110140
<nav className="flex-1 pt-2">
@@ -117,27 +147,35 @@ export default function RootLayout({
117147

118148
{/* Main content area */}
119149
<div className="flex h-screen flex-col">
120-
<header className="pointer-events-auto relative z-10 border-b border-gray-200/30 dark:border-white/[0.03] bg-muted/40 px-4 lg:px-6" data-header="main">
150+
<header
151+
className="pointer-events-auto relative z-10 border-b border-gray-200/30 bg-muted/40 px-4 dark:border-white/[0.03] lg:px-6"
152+
data-header="main"
153+
>
121154
<div className="flex h-14 items-center gap-4 lg:h-16">
122155
{/* Mobile menu button */}
123156
<MobileNavigation isWalletPath={isWalletPath} />
124-
157+
125158
{/* Logo in mobile header - centered */}
126-
<div className="flex-1 flex justify-center md:hidden">
127-
<Link href="/" className="flex items-center gap-2 px-2 py-1 rounded-md hover:bg-gray-100/50 dark:hover:bg-gray-800/50 transition-colors">
159+
<div className="flex flex-1 justify-center md:hidden">
160+
<Link
161+
href="/"
162+
className="flex items-center gap-2 rounded-md px-2 py-1 transition-colors hover:bg-gray-100/50 dark:hover:bg-gray-800/50"
163+
>
128164
<svg
129-
className="h-7 w-7 text-foreground flex-shrink-0"
165+
className="h-7 w-7 flex-shrink-0 text-foreground"
130166
enableBackground="new 0 0 300 200"
131167
viewBox="0 0 300 200"
132168
xmlns="http://www.w3.org/2000/svg"
133169
fill="currentColor"
134170
>
135171
<path d="m289 127-45-60-45-60c-.9-1.3-2.4-2-4-2s-3.1.7-4 2l-37 49.3c-2 2.7-6 2.7-8 0l-37-49.3c-.9-1.3-2.4-2-4-2s-3.1.7-4 2l-45 60-45 60c-1.3 1.8-1.3 4.2 0 6l45 60c.9 1.3 2.4 2 4 2s3.1-.7 4-2l37-49.3c2-2.7 6-2.7 8 0l37 49.3c.9 1.3 2.4 2 4 2s3.1-.7 4-2l37-49.3c2-2.7 6-2.7 8 0l37 49.3c.9 1.3 2.4 2 4 2s3.1-.7 4-2l45-60c1.3-1.8 1.3-4.2 0-6zm-90-103.3 32.5 43.3c1.3 1.8 1.3 4.2 0 6l-32.5 43.3c-2 2.7-6 2.7-8 0l-32.5-43.3c-1.3-1.8-1.3-4.2 0-6l32.5-43.3c2-2.7 6-2.7 8 0zm-90 0 32.5 43.3c1.3 1.8 1.3 4.2 0 6l-32.5 43.3c-2 2.7-6 2.7-8 0l-32.5-43.3c-1.3-1.8-1.3-4.2 0-6l32.5-43.3c2-2.7 6-2.7 8 0zm-53 152.6-32.5-43.3c-1.3-1.8-1.3-4.2 0-6l32.5-43.3c2-2.7 6-2.7 8 0l32.5 43.3c1.3 1.8 1.3 4.2 0 6l-32.5 43.3c-2 2.7-6 2.7-8 0zm90 0-32.5-43.3c-1.3-1.8-1.3-4.2 0-6l32.5-43.3c2-2.7 6-2.7 8 0l32.5 43.3c1.3 1.8 1.3 4.2 0 6l-32.5 43.3c-2 2.7-6 2.7-8 0zm90 0-32.5-43.3c-1.3-1.8-1.3-4.2 0-6l32.5-43.3c2-2.7 6-2.7 8 0l32.5 43.3c1.3 1.8 1.3 4.2 0 6l-32.5 43.3c-2 2.7-6 2.7-8 0z" />
136172
</svg>
137-
<span className="text-base font-medium text-foreground whitespace-nowrap">Multi-Sig Platform</span>
173+
<span className="whitespace-nowrap text-base font-medium text-foreground">
174+
Multi-Sig Platform
175+
</span>
138176
</Link>
139177
</div>
140-
178+
141179
{/* Wallet selection + breadcrumb row on desktop */}
142180
{isLoggedIn && (
143181
<div className="hidden md:block">
@@ -184,7 +222,7 @@ export default function RootLayout({
184222
) : (
185223
<>
186224
{/* Desktop buttons */}
187-
<div className="hidden md:flex items-center space-x-2">
225+
<div className="hidden items-center space-x-2 md:flex">
188226
<WalletDataLoaderWrapper mode="button" />
189227
<DialogReportWrapper mode="button" />
190228
<UserDropDownWrapper mode="button" />
@@ -194,7 +232,7 @@ export default function RootLayout({
194232
<WalletDataLoaderWrapper mode="menu-item" />
195233
<DialogReportWrapper mode="menu-item" />
196234
<UserDropDownWrapper mode="menu-item" />
197-
<div className="h-px bg-border -mx-2 my-1" />
235+
<div className="-mx-2 my-1 h-px bg-border" />
198236
<LogoutWrapper mode="menu-item" />
199237
</MobileActionsMenu>
200238
</>

src/components/pages/wallet/info/signers/card-edit-signers.tsx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export default function EditSigners({
5757
walletId: appWallet.id,
5858
signersDescriptions: signersDescriptions,
5959
signersStakeKeys: updatedStakeKeys,
60+
signersDRepKeys: updatedDRepKeys,
6061
});
6162
}
6263

@@ -77,6 +78,23 @@ export default function EditSigners({
7778
return skList;
7879
}, [signersAddresses, signersStakeKeys, userAddress, user?.stakeAddress]);
7980

81+
const updatedDRepKeys = useMemo(() => {
82+
const dkList: string[] = Array(signersAddresses.length).fill("");
83+
for (let i = 0; i < signersAddresses.length; i++) {
84+
const drepKey = user?.drepKeyHash;
85+
if (
86+
signersAddresses[i] === userAddress &&
87+
drepKey !== undefined &&
88+
!appWallet.signersDRepKeys?.[i]
89+
) {
90+
dkList[i] = drepKey;
91+
} else {
92+
dkList[i] = appWallet.signersDRepKeys?.[i] ?? "";
93+
}
94+
}
95+
return dkList;
96+
}, [signersAddresses, appWallet.signersDRepKeys, userAddress, user?.drepKeyHash]);
97+
8098
const newStakekey = (index: number) => {
8199
const stakeAddr = user?.stakeAddress;
82100
return (
@@ -86,6 +104,15 @@ export default function EditSigners({
86104
);
87105
};
88106

107+
const newDRepKey = (index: number) => {
108+
const drepKey = user?.drepKeyHash;
109+
return (
110+
signersAddresses[index] === userAddress &&
111+
drepKey !== undefined &&
112+
appWallet.signersDRepKeys?.[index] !== drepKey
113+
);
114+
};
115+
89116
return (
90117
<>
91118
<Table>
@@ -137,6 +164,30 @@ export default function EditSigners({
137164
Click Update to add your stake key to the multisig wallet.
138165
</Label>
139166
)}
167+
<div className="grid grid-cols-4 items-start gap-4">
168+
<Label className="text-right mt-2">DRep Key</Label>
169+
<textarea
170+
placeholder="drep1..."
171+
className={`col-span-3 flex min-h-[36px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 resize-none ${newDRepKey(index) && "text-green-500"}`}
172+
value={
173+
signersAddresses[index] === userAddress
174+
? user?.drepKeyHash ?? ""
175+
: appWallet.signersDRepKeys?.[index] ?? ""
176+
}
177+
disabled
178+
rows={1}
179+
onInput={(e) => {
180+
const target = e.target as HTMLTextAreaElement;
181+
target.style.height = 'auto';
182+
target.style.height = target.scrollHeight + 'px';
183+
}}
184+
/>
185+
</div>
186+
{newDRepKey(index) && (
187+
<Label className="text-right">
188+
Click Update to add your DRep key to the multisig wallet.
189+
</Label>
190+
)}
140191
<div className="grid grid-cols-4 items-center gap-4">
141192
<Label className="text-right">Description</Label>
142193
<Input

src/server/api/routers/users.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const userRouter = createTRPCRouter({
1818
z.object({
1919
address: z.string(),
2020
stakeAddress: z.string(),
21+
drepKeyHash: z.string(),
2122
nostrKey: z.string(),
2223
}),
2324
)
@@ -26,11 +27,56 @@ export const userRouter = createTRPCRouter({
2627
data: {
2728
address: input.address,
2829
stakeAddress: input.stakeAddress,
30+
drepKeyHash: input.drepKeyHash,
2931
nostrKey: input.nostrKey,
3032
},
3133
});
3234
}),
3335

36+
updateUser: publicProcedure
37+
.input(
38+
z.object({
39+
address: z.string().optional(),
40+
stakeAddress: z.string().optional(),
41+
drepKeyHash: z.string().optional(),
42+
}),
43+
)
44+
.mutation(async ({ ctx, input }) => {
45+
const { address, stakeAddress, drepKeyHash } = input;
46+
47+
if (!address && !stakeAddress && !drepKeyHash) {
48+
throw new Error("At least one of address, stakeAddress, or drepKeyHash must be provided.");
49+
}
50+
51+
const user = await ctx.db.user.findFirst({
52+
where: {
53+
OR: [
54+
address ? { address } : undefined,
55+
stakeAddress ? { stakeAddress } : undefined,
56+
drepKeyHash ? { drepKeyHash } : undefined,
57+
].filter(Boolean) as any,
58+
},
59+
});
60+
61+
if (!user) {
62+
throw new Error("User not found.");
63+
}
64+
65+
const data: Record<string, string> = {};
66+
if (address && address !== user.address) data.address = address;
67+
if (stakeAddress && stakeAddress !== user.stakeAddress) data.stakeAddress = stakeAddress;
68+
if (drepKeyHash && drepKeyHash !== user.drepKeyHash) data.drepKeyHash = drepKeyHash;
69+
70+
if (Object.keys(data).length === 0) {
71+
throw new Error("No updatable fields provided.");
72+
}
73+
74+
return ctx.db.user.update({
75+
where: { id: user.id },
76+
data,
77+
});
78+
}),
79+
3480
getNostrKeysByAddresses: publicProcedure
3581
.input(z.object({ addresses: z.array(z.string()) }))
3682
.query(async ({ ctx, input }) => {

src/server/api/routers/wallets.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ export const walletRouter = createTRPCRouter({
103103
z.object({
104104
walletId: z.string(),
105105
signersStakeKeys: z.array(z.string()),
106+
signersDRepKeys: z.array(z.string()),
106107
signersDescriptions: z.array(z.string()),
107108
}),
108109
)
@@ -114,6 +115,7 @@ export const walletRouter = createTRPCRouter({
114115
data: {
115116
signersDescriptions: input.signersDescriptions,
116117
signersStakeKeys: input.signersStakeKeys,
118+
signersDRepKeys: input.signersDRepKeys,
117119
},
118120
});
119121
}),

0 commit comments

Comments
 (0)