All checks were successful
CI / lint-and-test (push) Successful in 9m52s
Co-authored-by: Cursor <cursoragent@cursor.com>
125 lines
4.3 KiB
TypeScript
125 lines
4.3 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { useWriteContract, useReadContract } from "wagmi";
|
|
import { keccak256, toBytes } from "viem";
|
|
import {
|
|
CONTRACT_ADDRESSES,
|
|
SUB_ACCOUNT_FACTORY_ABI,
|
|
} from "@/lib/web3/contracts";
|
|
import { formatAddress } from "@/lib/utils";
|
|
import { useTreasury } from "@/lib/hooks/useTreasury";
|
|
import { fetchSubAccounts, type SubAccountRecord } from "@/lib/api/client";
|
|
|
|
export function SubAccountsSettings() {
|
|
const { treasuryId } = useTreasury();
|
|
const [label, setLabel] = useState("");
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState("");
|
|
const [subAccounts, setSubAccounts] = useState<SubAccountRecord[]>([]);
|
|
const { writeContractAsync } = useWriteContract();
|
|
|
|
const { data: onChainAccounts } = useReadContract({
|
|
address: CONTRACT_ADDRESSES.SubAccountFactory as `0x${string}`,
|
|
abi: SUB_ACCOUNT_FACTORY_ABI,
|
|
functionName: "getSubAccounts",
|
|
args: [CONTRACT_ADDRESSES.TreasuryWallet as `0x${string}`],
|
|
query: { enabled: Boolean(CONTRACT_ADDRESSES.SubAccountFactory && CONTRACT_ADDRESSES.TreasuryWallet) },
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (!treasuryId) return;
|
|
fetchSubAccounts(treasuryId)
|
|
.then(setSubAccounts)
|
|
.catch(() => setSubAccounts([]));
|
|
}, [treasuryId]);
|
|
|
|
const handleCreate = async () => {
|
|
setError("");
|
|
setLoading(true);
|
|
try {
|
|
if (!CONTRACT_ADDRESSES.SubAccountFactory || !CONTRACT_ADDRESSES.TreasuryWallet) {
|
|
throw new Error("Factory or treasury not configured");
|
|
}
|
|
if (!treasuryId) {
|
|
throw new Error("Treasury not loaded from backend");
|
|
}
|
|
|
|
const metadataHash = keccak256(toBytes(label.trim() || `sub-${Date.now()}`));
|
|
await writeContractAsync({
|
|
address: CONTRACT_ADDRESSES.SubAccountFactory as `0x${string}`,
|
|
abi: SUB_ACCOUNT_FACTORY_ABI,
|
|
functionName: "createSubAccount",
|
|
args: [CONTRACT_ADDRESSES.TreasuryWallet as `0x${string}`, metadataHash],
|
|
});
|
|
|
|
setLabel("");
|
|
const refreshed = await fetchSubAccounts(treasuryId);
|
|
setSubAccounts(refreshed);
|
|
} catch (err: unknown) {
|
|
setError(err instanceof Error ? err.message : "Failed to create sub-account");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const chainList = (onChainAccounts as `0x${string}`[] | undefined) ?? [];
|
|
|
|
return (
|
|
<div className="bg-gray-900 rounded-xl p-8">
|
|
<h2 className="text-2xl font-semibold mb-4">Sub-Accounts</h2>
|
|
<p className="text-sm text-gray-400 mb-6">
|
|
Create child treasury wallets that inherit the main multisig configuration.
|
|
</p>
|
|
|
|
<div className="flex gap-4 mb-6">
|
|
<input
|
|
type="text"
|
|
value={label}
|
|
onChange={(e) => setLabel(e.target.value)}
|
|
placeholder="Label (optional)"
|
|
className="flex-1 bg-gray-800 rounded-lg p-3"
|
|
/>
|
|
<button
|
|
type="button"
|
|
onClick={handleCreate}
|
|
disabled={loading || !treasuryId}
|
|
className="px-6 py-3 bg-purple-600 hover:bg-purple-700 disabled:bg-gray-700 rounded-lg transition-colors"
|
|
>
|
|
{loading ? "Creating..." : "Create Sub-Account"}
|
|
</button>
|
|
</div>
|
|
|
|
{error && (
|
|
<div className="bg-red-900/20 border border-red-600 rounded-lg p-4 mb-4">
|
|
<p className="text-red-400 text-sm">{error}</p>
|
|
</div>
|
|
)}
|
|
|
|
<div className="space-y-2">
|
|
<h3 className="text-sm font-medium text-gray-300 mb-2">Registered accounts</h3>
|
|
{subAccounts.length === 0 && chainList.length === 0 ? (
|
|
<p className="text-gray-500 text-sm">No sub-accounts yet</p>
|
|
) : (
|
|
[...new Set([
|
|
...subAccounts.map((a) => a.address),
|
|
...chainList.map((a) => a.toLowerCase()),
|
|
])].map((address) => (
|
|
<div
|
|
key={address}
|
|
className="bg-gray-800 rounded-lg p-3 font-mono text-sm flex justify-between items-center"
|
|
>
|
|
<span>{formatAddress(address)}</span>
|
|
{subAccounts.find((a) => a.address.toLowerCase() === address.toLowerCase())?.label && (
|
|
<span className="text-xs text-gray-400">
|
|
{subAccounts.find((a) => a.address.toLowerCase() === address.toLowerCase())?.label}
|
|
</span>
|
|
)}
|
|
</div>
|
|
))
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|