Files
solace-bg-dubai/frontend/components/settings/SubAccountsSettings.tsx
defiQUG a03417be98
All checks were successful
CI / lint-and-test (push) Successful in 9m52s
chore: consolidate local WIP (repo cleanup 20260707)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 09:41:38 -07:00

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>
);
}