Skip to content

Commit 35fa50c

Browse files
Merge branch 'main' into fix/minor-improvements
2 parents 697a035 + ceca81d commit 35fa50c

File tree

2 files changed

+98
-4
lines changed

2 files changed

+98
-4
lines changed

packages/nextjs/components/address-vision/ButtonsCard.tsx

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { usePublicClient } from "wagmi";
88
import { useAddressStore } from "~~/services/store/store";
99

1010
const GNOSIS_SAFE_BYTECODE_PATTERN = "0x608060405273ffffffffffffffffffffffffffffffffffffffff600054167fa619486e";
11+
const EOF_SIGNATURE = "0xef01";
1112

1213
const SAFE_ABI = [
1314
{
@@ -31,6 +32,12 @@ export const ButtonsCard = () => {
3132
const [isGnosisSafe, setIsGnosisSafe] = useState<boolean>(false);
3233
const [safeOwners, setSafeOwners] = useState<Address[]>([]);
3334
const [safeThreshold, setSafeThreshold] = useState<number>(0);
35+
const [isValidator, setIsValidator] = useState<boolean>(false);
36+
const [validatorInfo, setValidatorInfo] = useState<{
37+
totalDeposits: number;
38+
totalETHStaked: string;
39+
pubkeys: string[];
40+
} | null>(null);
3441

3542
const { resolvedAddress: address } = useAddressStore();
3643
const client = usePublicClient();
@@ -42,14 +49,16 @@ export const ButtonsCard = () => {
4249
setIsGnosisSafe(false);
4350
setSafeOwners([]);
4451
setSafeThreshold(0);
52+
setIsValidator(false);
53+
setValidatorInfo(null);
4554
}, [address]);
4655

4756
useEffect(() => {
4857
const checkGnosisSafe = async () => {
4958
if (!address || !client) return;
5059
try {
5160
const bytecode = await client.getCode({ address });
52-
const isContract = bytecode && bytecode.length > 2;
61+
const isContract = bytecode && bytecode.length > 2 && !bytecode.startsWith(EOF_SIGNATURE);
5362

5463
setIsContractAddress(isContract || false);
5564

@@ -77,7 +86,54 @@ export const ButtonsCard = () => {
7786
}
7887
};
7988

89+
const checkValidator = async () => {
90+
if (!address) return;
91+
try {
92+
const response = await fetch(`https://beaconcha.in/api/v1/validator/eth1/${address}`);
93+
94+
if (response.ok) {
95+
const data = await response.json();
96+
97+
if (data.status === "OK" && data.data && data.data.length > 0) {
98+
const validatorIndices = data.data.map((validator: any) => validator.validatorindex);
99+
100+
const validatorPromises = validatorIndices.map(async (index: number) => {
101+
try {
102+
const validatorResponse = await fetch(`https://beaconcha.in/api/v1/validator/${index}`);
103+
if (validatorResponse.ok) {
104+
const validatorData = await validatorResponse.json();
105+
if (validatorData.status === "OK" && validatorData.data) {
106+
return {
107+
balance: parseFloat(validatorData.data.balance) / 1e9,
108+
pubkey: validatorData.data.pubkey,
109+
};
110+
}
111+
}
112+
return { balance: 0, pubkey: "" };
113+
} catch (error) {
114+
return { balance: 0, pubkey: "" };
115+
}
116+
});
117+
118+
const validators = await Promise.all(validatorPromises);
119+
const totalBalance = validators.reduce((sum, validator) => sum + validator.balance, 0);
120+
const pubkeys = validators.map(validator => validator.pubkey).filter(pubkey => pubkey !== "");
121+
122+
setIsValidator(true);
123+
setValidatorInfo({
124+
totalDeposits: data.data.length,
125+
totalETHStaked: totalBalance.toFixed(4),
126+
pubkeys: pubkeys,
127+
});
128+
}
129+
}
130+
} catch (error) {
131+
console.error("Validator check failed:", error);
132+
}
133+
};
134+
80135
checkGnosisSafe();
136+
checkValidator();
81137
}, [address, client]);
82138

83139
if (isContractAddress && !isGnosisSafe) {
@@ -96,6 +152,43 @@ export const ButtonsCard = () => {
96152
);
97153
}
98154

155+
if (isValidator) {
156+
return (
157+
<div className="card w-[370px] md:w-[425px] bg-base-100 shadow-xl">
158+
<div className="card-body">
159+
<h2 className="card-title">
160+
<span className="text-2xl mr-2">🔗</span>
161+
Ethereum Validator
162+
</h2>
163+
<div className="space-y-2">
164+
<div className="flex justify-between items-center">
165+
<p className="m-0 font-semibold">Validators:</p>
166+
<p className="m-0">{validatorInfo?.totalDeposits || 0}</p>
167+
</div>
168+
<div className="flex justify-between items-center">
169+
<p className="m-0 font-semibold">Total Balance:</p>
170+
<p className="m-0">{validatorInfo?.totalETHStaked || "0"} ETH</p>
171+
</div>
172+
<div className="mt-4">
173+
<Link
174+
href={
175+
validatorInfo?.pubkeys && validatorInfo.pubkeys.length === 1
176+
? `https://beaconcha.in/validator/${validatorInfo.pubkeys[0]}`
177+
: `https://beaconcha.in/validators/eth1deposits?q=${address}`
178+
}
179+
className="btn btn-primary btn-sm rounded-full w-full"
180+
target="_blank"
181+
rel="noopener noreferrer"
182+
>
183+
View on Beaconcha.in
184+
</Link>
185+
</div>
186+
</div>
187+
</div>
188+
</div>
189+
);
190+
}
191+
99192
if (isContractAddress && isGnosisSafe) {
100193
return (
101194
<div className="card w-[370px] md:w-[425px] bg-base-100 shadow-xl">

packages/nextjs/components/address-vision/TokensTable.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,16 @@ export const TokensTable = ({ tokens }: { tokens: Token[] }) => {
1212
const divisor = BigInt(Math.pow(10, decimals));
1313
const integerPart = balanceBigInt / divisor;
1414
const fractionalPart = balanceBigInt % divisor;
15-
const formattedFractionalPart = fractionalPart.toString().padStart(decimals, "0").slice(0, 2);
15+
const decimalDigits = integerPart === 0n ? 6 : integerPart < 10n ? 4 : 2;
16+
const formattedFractionalPart = fractionalPart.toString().padStart(decimals, "0").slice(0, decimalDigits);
1617

1718
const integerPartStr = integerPart.toString();
1819

1920
if (integerPartStr.length > 10) {
2021
return `${integerPartStr.slice(0, 10)}...`;
21-
} else {
22-
return `${integerPartStr}.${formattedFractionalPart}`;
2322
}
23+
const formatted = `${integerPartStr}.${formattedFractionalPart}`;
24+
return formatted === "0.000000" ? "0" : formatted;
2425
};
2526

2627
if (tokens.length === 0) {

0 commit comments

Comments
 (0)