Skip to content

Commit 6566ea7

Browse files
authored
Merge branch 'main' into feature/added-leaderboard-page
2 parents a845bf1 + f67d93b commit 6566ea7

17 files changed

Lines changed: 552 additions & 61 deletions

File tree

README.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -181,13 +181,13 @@ A community-driven badge registry where anyone can create badges with unique nam
181181
- **Gas-efficient**: Simple storage patterns
182182
- **Event-driven**: Emits events for badge creation
183183

184-
**Contract Interface:**
184+
**Contract Interface (V2 - current):**
185185
```solidity
186186
// Create a new badge
187-
function createBadge(bytes32 name, bytes32 description) external
187+
function createBadge(bytes32 name, bytes calldata description) external
188188
189189
// Get badge information
190-
function getBadge(bytes32 name) external view returns (bytes32, bytes32, address)
190+
function getBadge(bytes32 name) external view returns (bytes32, bytes memory, address)
191191
192192
// Check if badge exists
193193
function exists(bytes32 name) external view returns (bool)
@@ -197,13 +197,16 @@ function totalBadges() external view returns (uint256)
197197
198198
// Enumerate badges
199199
function badgeNameAt(uint256 index) external view returns (bytes32)
200+
function getBadgeAt(uint256 index) external view returns (bytes32, bytes memory, address)
200201
```
201202

202203
**Events:**
203204
```solidity
204-
event BadgeCreated(bytes32 indexed name, bytes32 description, address indexed creator)
205+
event BadgeCreated(bytes32 indexed name, bytes description, address indexed creator)
205206
```
206207

208+
> **Note:** V1 contracts used `bytes32` for descriptions (max 32 chars). V2 uses `bytes` for unlimited length. The frontend is retrocompatible with both versions.
209+
207210
### TheGuildActivityToken (TGA)
208211

209212
An ERC20 token used to reward attestations. Ownable; the owner is the attestation resolver contract.

frontend/docs/V2_CLEANUP.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# BadgeRegistry V2 Cleanup Guide
2+
3+
## Overview
4+
5+
This document outlines the cleanup steps required after full V2 deployment of BadgeRegistry contracts. The current codebase includes retro-compatibility logic to support both V1 and V2 contracts during the migration period. Once all registries are upgraded to V2, this temporary code can be removed.
6+
7+
## Files to Delete
8+
9+
- `frontend/src/lib/utils/abiDetection.ts` - Error-based ABI detection utilities
10+
- `frontend/src/lib/badges/registryVersion.ts` - Version detection module
11+
12+
## Functions / Logic to Remove
13+
14+
- `detectBadgeRegistryVersion()` - Version detection function
15+
- `isDecodeError()` - Decode error detection utility
16+
- `isFunctionSelectorError()` - Function selector error detection utility
17+
- `buildCreateBadgeArgs()` - Conditional argument builder for V1/V2 differences
18+
- Any version-probe logic / error-based ABI inference
19+
20+
## Hook Simplifications
21+
22+
### use-create-badge.ts
23+
24+
- Remove version detection and conditional ABI selection
25+
- Remove `detectBadgeRegistryVersion()` call
26+
- Always use `badgeRegistryAbiV2` (remove conditional `finalAbiMode === "v2" ? badgeRegistryAbiV2 : badgeRegistryAbiV1`)
27+
- Always use `bytes` description format: replace `buildCreateBadgeArgs()` with direct `stringToBytes(description)`
28+
- Remove `badgeRegistryAbiV1` import
29+
- Remove `detectBadgeRegistryVersion` import
30+
- Remove `buildCreateBadgeArgs` import
31+
32+
### use-get-badges.ts
33+
34+
- Remove version probing / `abiMode` inference
35+
- Remove `versionProbeQuery` query
36+
- Remove `abiMode` useMemo logic
37+
- Always use `badgeRegistryAbiV2` in `badgeContracts` (remove conditional ABI selection)
38+
- Always decode description as `bytes` using `bytesToString()` (remove conditional `bytesToString` vs `bytes32ToString`)
39+
- Remove `isDecodeError` import and usage
40+
- Remove `badgeRegistryAbiV1` import
41+
42+
## Expected End State
43+
44+
- Only V2 ABI (`badgeRegistryAbiV2`) used throughout the codebase
45+
- No fallback branches or conditional logic based on contract version
46+
- No error-based ABI detection or version probing
47+
- Simpler, more maintainable codebase with reduced complexity
48+

frontend/src/components/AppWrapper.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,17 @@ import { ActivityTokenBalance } from "@/components/ActivityTokenBalance";
1313
import { Background } from "@/components/Background";
1414
import { LoginButton } from "@/components/LoginButton";
1515

16-
const queryClient = new QueryClient();
16+
const queryClient = new QueryClient({
17+
defaultOptions: {
18+
queries: {
19+
staleTime: 60_000, // 1 minute - badges change infrequently
20+
refetchOnWindowFocus: false, // Prevent refetch storms on alt-tab
21+
refetchOnReconnect: false, // Prevent refetch storms on network reconnect
22+
refetchOnMount: false, // Prevent refetch on component remount (cache is fresh)
23+
retry: 1, // Single retry for transient errors
24+
},
25+
},
26+
});
1727

1828
interface AppWrapperProps {
1929
children: React.ReactNode;

frontend/src/components/badges/BadgesList.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const ICONS: Record<string, string> = {
2626
};
2727

2828
export function BadgesList(): React.ReactElement {
29-
const { data, isLoading, error } = useGetBadges();
29+
const { data, isLoading, error, refetch } = useGetBadges();
3030
const [searchQuery, setSearchQuery] = useState("");
3131
const list = (data && data.length > 0 ? data : HARD_CODED_BADGES) as Badge[];
3232

@@ -62,7 +62,7 @@ export function BadgesList(): React.ReactElement {
6262
/>
6363
</div>
6464

65-
<CreateBadgeButton />
65+
<CreateBadgeButton onBadgeCreated={refetch} />
6666
</div>
6767

6868
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">

frontend/src/components/badges/CreateBadgeButton.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import {
2323
import { useForm } from "react-hook-form";
2424
import { z } from "zod";
2525
import { zodResolver } from "@hookform/resolvers/zod";
26-
import { useGetBadges } from "@/hooks/badges/use-get-badges";
2726

2827
const formSchema = z.object({
2928
name: z.string().min(1, { message: "Name is required." }).max(32),
@@ -32,11 +31,14 @@ const formSchema = z.object({
3231

3332
type FormValues = z.infer<typeof formSchema>;
3433

35-
export function CreateBadgeButton() {
34+
interface CreateBadgeButtonProps {
35+
onBadgeCreated?: () => void;
36+
}
37+
38+
export function CreateBadgeButton({ onBadgeCreated }: CreateBadgeButtonProps) {
3639
const [open, setOpen] = useState(false);
3740
const { createBadge, isPending, error, reset, isConfirmed, isConfirming } =
3841
useCreateBadge();
39-
const { refetch } = useGetBadges();
4042

4143
const form = useForm<FormValues>({
4244
resolver: zodResolver(formSchema),
@@ -49,12 +51,12 @@ export function CreateBadgeButton() {
4951

5052
useEffect(() => {
5153
if (isConfirmed) {
52-
refetch();
54+
onBadgeCreated?.();
5355
setOpen(false);
5456
form.reset();
5557
reset();
5658
}
57-
}, [isConfirmed, refetch, form, reset]);
59+
}, [isConfirmed, onBadgeCreated, form, reset]);
5860

5961
return (
6062
<Dialog open={open} onOpenChange={setOpen}>

frontend/src/components/displayError/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ type ErrorDisplayProps = {
33
};
44

55
export default function ErrorDisplay({ error }: ErrorDisplayProps) {
6-
if (!error) return null;
6+
if (!error) return null;
77

88
return (
99
<p className="text-2xl text-yellow-600 flex items-center gap-2">

frontend/src/hooks/attestations/use-create-attestation.ts

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,7 @@ import {
1212
EAS_CONTRACT_ADDRESS,
1313
SCHEMA_ID,
1414
} from "@/lib/constants/blockchainConstants";
15-
16-
function stringToBytes32(value: string): `0x${string}` {
17-
const encoder = new TextEncoder();
18-
const bytes = encoder.encode(value);
19-
const out = new Uint8Array(32);
20-
const len = Math.min(32, bytes.length);
21-
for (let i = 0; i < len; i++) out[i] = bytes[i];
22-
let hex = "0x";
23-
for (let i = 0; i < out.length; i++)
24-
hex += out[i].toString(16).padStart(2, "0");
25-
return hex as `0x${string}`;
26-
}
27-
28-
function stringToBytes(value: string): `0x${string}` {
29-
const encoder = new TextEncoder();
30-
const bytes = encoder.encode(value);
31-
let hex = "0x";
32-
for (let i = 0; i < bytes.length; i++)
33-
hex += bytes[i].toString(16).padStart(2, "0");
34-
return hex as `0x${string}`;
35-
}
15+
import { stringToBytes32, stringToBytes } from "@/lib/utils/blockchainUtils";
3616

3717
function encodeBadgeData(
3818
badgeName: `0x${string}`,
@@ -69,7 +49,7 @@ export function useCreateAttestation() {
6949
);
7050
}
7151
isBusyRef.current = true;
72-
// Convert strings to bytes
52+
// Convert strings to bytes32
7353
const badgeNameBytes = stringToBytes32(badgeName);
7454
const justificationBytes = stringToBytes(justification);
7555

@@ -117,6 +97,7 @@ export function useCreateAttestation() {
11797

11898
const wait = useWaitForTransactionReceipt({
11999
hash: hash as `0x${string}` | undefined,
100+
confirmations: 6,
120101
query: { enabled: Boolean(hash) },
121102
});
122103

frontend/src/hooks/badges/use-create-badge.ts

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,66 @@
11
import { useMemo } from "react";
2-
import { useWriteContract, useWaitForTransactionReceipt } from "wagmi";
2+
import { useWriteContract, useWaitForTransactionReceipt, useAccount, useConfig } from "wagmi";
3+
import { simulateContract, readContract } from "@wagmi/core";
34
import { BADGE_REGISTRY_ADDRESS } from "@/lib/constants/blockchainConstants";
4-
import { badgeRegistryAbi } from "@/lib/abis/badgeRegistryAbi";
5-
import { stringToBytes32 } from "@/lib/utils/blockchainUtils";
5+
import {
6+
badgeRegistryAbiV1,
7+
badgeRegistryAbiV2,
8+
} from "@/lib/abis/badgeRegistryAbi";
9+
import { stringToBytes32, buildCreateBadgeArgs } from "@/lib/utils/blockchainUtils";
10+
import { detectBadgeRegistryVersion } from "@/lib/badges/registryVersion";
611

712
export function useCreateBadge() {
13+
const config = useConfig();
14+
const { address: account, chainId } = useAccount();
815
const { writeContractAsync, isPending, error, data, reset } =
916
useWriteContract();
1017

1118
const createBadge = useMemo(() => {
1219
return async (name: string, description: string) => {
13-
if (!BADGE_REGISTRY_ADDRESS) throw new Error("Missing registry address");
20+
if (!BADGE_REGISTRY_ADDRESS) {
21+
throw new Error("Badge registry address not configured");
22+
}
23+
if (!account) {
24+
throw new Error("No wallet connected");
25+
}
26+
if (!chainId) {
27+
throw new Error("No chain ID available");
28+
}
29+
1430
const nameBytes = stringToBytes32(name);
15-
const descriptionBytes = stringToBytes32(description);
16-
return writeContractAsync({
17-
abi: badgeRegistryAbi,
31+
32+
// Determine ABI mode deterministically BEFORE sending transaction
33+
// All version detection happens on-demand here to prevent refetch storm
34+
// Fetch totalBadges on-demand
35+
const totalBadgesResult = await readContract(config, {
36+
abi: badgeRegistryAbiV2,
37+
address: BADGE_REGISTRY_ADDRESS,
38+
functionName: "totalBadges",
39+
});
40+
const currentCount = Number(totalBadgesResult ?? 0n);
41+
42+
// TODO(cleanup-after-v2): Remove V1 fallback logic after V2 full deployment. Always use V2 ABI. See docs/V2_CLEANUP.md.
43+
const finalAbiMode = await detectBadgeRegistryVersion(
44+
config,
45+
account as `0x${string}`,
46+
chainId,
47+
currentCount
48+
);
49+
50+
// Now that ABI mode is determined, simulate and send with correct ABI
51+
const simulation = await simulateContract(config, {
52+
abi: finalAbiMode === "v2" ? badgeRegistryAbiV2 : badgeRegistryAbiV1,
1853
address: BADGE_REGISTRY_ADDRESS,
1954
functionName: "createBadge",
20-
args: [nameBytes, descriptionBytes],
55+
args: buildCreateBadgeArgs(nameBytes, description, finalAbiMode),
56+
account: account as `0x${string}` | undefined,
57+
chainId,
2158
});
59+
60+
// Single writeContractAsync call with correct ABI
61+
return await writeContractAsync(simulation.request);
2262
};
23-
}, [writeContractAsync]);
63+
}, [writeContractAsync, account, chainId, config]);
2464

2565
const wait = useWaitForTransactionReceipt({
2666
hash: data as `0x${string}` | undefined,

0 commit comments

Comments
 (0)