From 78a85ac1dc78bd60c3558a23db90dadd07fc1f50 Mon Sep 17 00:00:00 2001 From: Antoine Estienne Date: Mon, 8 Sep 2025 18:23:49 +0200 Subject: [PATCH 1/5] add TheGuildBadgeRegistry and foundry repo --- .gitmodules | 3 + README.md | 72 +++++++++++++++++- .../.github/workflows/test.yml | 40 ++++++++++ the-guild-smart-contracts/.gitignore | 14 ++++ the-guild-smart-contracts/README.md | 68 +++++++++++++++++ the-guild-smart-contracts/foundry.lock | 8 ++ the-guild-smart-contracts/foundry.toml | 6 ++ the-guild-smart-contracts/lib/forge-std | 1 + .../script/Counter.s.sol | 19 +++++ .../script/TheGuildBadgeRegistry.s.sol | 13 ++++ the-guild-smart-contracts/src/Counter.sol | 14 ++++ .../src/TheGuildBadgeRegistry.sol | 74 +++++++++++++++++++ the-guild-smart-contracts/test/Counter.t.sol | 24 ++++++ .../test/TheGuildBadgeRegistry.t.sol | 55 ++++++++++++++ 14 files changed, 409 insertions(+), 2 deletions(-) create mode 100644 .gitmodules create mode 100644 the-guild-smart-contracts/.github/workflows/test.yml create mode 100644 the-guild-smart-contracts/.gitignore create mode 100644 the-guild-smart-contracts/README.md create mode 100644 the-guild-smart-contracts/foundry.lock create mode 100644 the-guild-smart-contracts/foundry.toml create mode 160000 the-guild-smart-contracts/lib/forge-std create mode 100644 the-guild-smart-contracts/script/Counter.s.sol create mode 100644 the-guild-smart-contracts/script/TheGuildBadgeRegistry.s.sol create mode 100644 the-guild-smart-contracts/src/Counter.sol create mode 100644 the-guild-smart-contracts/src/TheGuildBadgeRegistry.sol create mode 100644 the-guild-smart-contracts/test/Counter.t.sol create mode 100644 the-guild-smart-contracts/test/TheGuildBadgeRegistry.t.sol diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..40000dd --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "the-guild-smart-contracts/lib/forge-std"] + path = the-guild-smart-contracts/lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/README.md b/README.md index 6b13b2e..35dbea7 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ This is a monorepo containing: - **`frontend/`** - Astro + React frontend with Web3 integration - **`backend/`** - Rust backend with Axum, SQLx, and SIWE authentication +- **`the-guild-smart-contracts/`** - Foundry-based Solidity smart contracts for badge registry ## Tech Stack @@ -30,12 +31,17 @@ This is a monorepo containing: - **PostgreSQL** - Database - **SIWE** - Sign-In with Ethereum authentication +### Smart Contracts +- **Solidity** - Smart contract programming language +- **Foundry** - Fast, portable and modular toolkit for Ethereum application development + ## Quick Start ### Prerequisites - [Nix](https://nixos.org/download.html) with flakes enabled - [direnv](https://direnv.net/) (optional, for automatic environment loading) - [just](https://github.com/casey/just) (command runner) +- [Foundry](https://book.getfoundry.sh/getting-started/installation) (for smart contracts) ### Setup @@ -102,6 +108,30 @@ just db-reset just db-stop ``` +#### Smart Contracts Development + +```bash +# Navigate to smart contracts directory +cd the-guild-smart-contracts + +# Build contracts +forge build + +# Run tests +forge test + +# Run tests with verbose output +forge test -vv + +# Deploy to local network (Anvil) +anvil +# In another terminal: +forge script script/TheGuildBadgeRegistry.s.sol:TheGuildBadgeRegistryScript --rpc-url http://localhost:8545 --private-key --broadcast + +# Deploy to testnet/mainnet +forge script script/TheGuildBadgeRegistry.s.sol:TheGuildBadgeRegistryScript --rpc-url --private-key --broadcast +``` + ### Available Commands Run `just help` to see all available commands: @@ -113,6 +143,44 @@ Run `just help` to see all available commands: - **Code Quality:** `just lint`, `just format` - **Utilities:** `just clean`, `just help` +## Smart Contracts + +The `the-guild-smart-contracts/` directory contains our Solidity smart contracts built with Foundry. + +### TheGuildBadgeRegistry + +A community-driven badge registry where anyone can create badges with unique names and descriptions. + +**Key Features:** +- **Community-driven**: Anyone can create badges +- **Unique names**: No duplicate badge names allowed +- **Immutable**: No owner or upgrade mechanism +- **Gas-efficient**: Simple storage patterns +- **Event-driven**: Emits events for badge creation + +**Contract Interface:** +```solidity +// Create a new badge +function createBadge(bytes32 name, bytes32 description) external + +// Get badge information +function getBadge(bytes32 name) external view returns (bytes32, bytes32, address) + +// Check if badge exists +function exists(bytes32 name) external view returns (bool) + +// Get total number of badges +function totalBadges() external view returns (uint256) + +// Enumerate badges +function badgeNameAt(uint256 index) external view returns (bytes32) +``` + +**Events:** +```solidity +event BadgeCreated(bytes32 indexed name, bytes32 description, address indexed creator) +``` + ## Features ### V0 (Current) @@ -121,12 +189,12 @@ Run `just help` to see all available commands: - [x] Rust backend with Axum - [x] Web3 wallet integration - [x] Basic profile and badge system +- [x] Smart contracts for on-chain badges - [ ] SIWE authentication - [ ] Database models and migrations - [ ] API endpoints for profiles and badges ### V1+ (Future) -- [ ] Smart contracts for on-chain badges - [ ] Gasless transactions - [ ] Badge hierarchy and categories - [ ] Activity and contribution tokens @@ -154,7 +222,7 @@ This project uses Nix for development instead of Docker because: ## Contributing -This is a community-driven project. Join our Discord to discuss features, propose changes, and contribute to the codebase. +This is a community-driven project. Join our [Discord](https://discord.gg/pg4UgaTr) to discuss features, propose changes, and contribute to the codebase. ## License diff --git a/the-guild-smart-contracts/.github/workflows/test.yml b/the-guild-smart-contracts/.github/workflows/test.yml new file mode 100644 index 0000000..4481ec6 --- /dev/null +++ b/the-guild-smart-contracts/.github/workflows/test.yml @@ -0,0 +1,40 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + +env: + FOUNDRY_PROFILE: ci + +jobs: + check: + name: Foundry project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Show Forge version + run: | + forge --version + + - name: Run Forge fmt + run: | + forge fmt --check + id: fmt + + - name: Run Forge build + run: | + forge build --sizes + id: build + + - name: Run Forge tests + run: | + forge test -vvv + id: test diff --git a/the-guild-smart-contracts/.gitignore b/the-guild-smart-contracts/.gitignore new file mode 100644 index 0000000..85198aa --- /dev/null +++ b/the-guild-smart-contracts/.gitignore @@ -0,0 +1,14 @@ +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +!/broadcast +/broadcast/*/31337/ +/broadcast/**/dry-run/ + +# Docs +docs/ + +# Dotenv file +.env diff --git a/the-guild-smart-contracts/README.md b/the-guild-smart-contracts/README.md new file mode 100644 index 0000000..5475eff --- /dev/null +++ b/the-guild-smart-contracts/README.md @@ -0,0 +1,68 @@ +## TheGuild Smart Contracts + +The idea is to be able to create badges and then send them to other users (denominated by their address). + +### Badges +Anybody can create a badge. The idea is to let the community input whatever they want and see what they come up with. Badges should be reused and have a description and a unique name. + +We will create a smart contract TheGuildBadgeRegistry that will have a list of badges with unique non-duplicate names. + +### Attestations +Then, we let users create an attestation of a badge to another user. The attestation can contain an optional justification (link to a project, or text explanation). + +To do this, we can just use EAS’ already deployed contracts. First we will register our schema (using their sdk or their UI): "bytes32 badgeName, bytes32 justification". Then, in the front end, we can use their sdk to create attestation from one user to another, referencing our schema id, the unique badge name, and a justification. We can also use EAS Resolver contract to prevent duplicate badges and reward attestations with Activity Token. + + +## Foundry Usage + +https://book.getfoundry.sh/ + +### Build + +```shell +$ forge build +``` + +### Test + +```shell +$ forge test +``` + +### Format + +```shell +$ forge fmt +``` + +### Gas Snapshots + +```shell +$ forge snapshot +``` + +### Anvil + +```shell +$ anvil +``` + +### Deploy + +```shell +$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key +``` + +### Cast + +```shell +$ cast +``` + +### Help + +```shell +$ forge --help +$ anvil --help +$ cast --help +``` diff --git a/the-guild-smart-contracts/foundry.lock b/the-guild-smart-contracts/foundry.lock new file mode 100644 index 0000000..5643642 --- /dev/null +++ b/the-guild-smart-contracts/foundry.lock @@ -0,0 +1,8 @@ +{ + "lib/forge-std": { + "tag": { + "name": "v1.10.0", + "rev": "8bbcf6e3f8f62f419e5429a0bd89331c85c37824" + } + } +} \ No newline at end of file diff --git a/the-guild-smart-contracts/foundry.toml b/the-guild-smart-contracts/foundry.toml new file mode 100644 index 0000000..25b918f --- /dev/null +++ b/the-guild-smart-contracts/foundry.toml @@ -0,0 +1,6 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/the-guild-smart-contracts/lib/forge-std b/the-guild-smart-contracts/lib/forge-std new file mode 160000 index 0000000..8bbcf6e --- /dev/null +++ b/the-guild-smart-contracts/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 8bbcf6e3f8f62f419e5429a0bd89331c85c37824 diff --git a/the-guild-smart-contracts/script/Counter.s.sol b/the-guild-smart-contracts/script/Counter.s.sol new file mode 100644 index 0000000..f01d69c --- /dev/null +++ b/the-guild-smart-contracts/script/Counter.s.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script} from "forge-std/Script.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterScript is Script { + Counter public counter; + + function setUp() public {} + + function run() public { + vm.startBroadcast(); + + counter = new Counter(); + + vm.stopBroadcast(); + } +} diff --git a/the-guild-smart-contracts/script/TheGuildBadgeRegistry.s.sol b/the-guild-smart-contracts/script/TheGuildBadgeRegistry.s.sol new file mode 100644 index 0000000..aea97ac --- /dev/null +++ b/the-guild-smart-contracts/script/TheGuildBadgeRegistry.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Script} from "forge-std/Script.sol"; +import {TheGuildBadgeRegistry} from "../src/TheGuildBadgeRegistry.sol"; + +contract TheGuildBadgeRegistryScript is Script { + function run() public { + vm.startBroadcast(); + new TheGuildBadgeRegistry(); + vm.stopBroadcast(); + } +} diff --git a/the-guild-smart-contracts/src/Counter.sol b/the-guild-smart-contracts/src/Counter.sol new file mode 100644 index 0000000..aded799 --- /dev/null +++ b/the-guild-smart-contracts/src/Counter.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} diff --git a/the-guild-smart-contracts/src/TheGuildBadgeRegistry.sol b/the-guild-smart-contracts/src/TheGuildBadgeRegistry.sol new file mode 100644 index 0000000..0e2345f --- /dev/null +++ b/the-guild-smart-contracts/src/TheGuildBadgeRegistry.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +/// @title TheGuildBadgeRegistry +/// @notice Minimal registry of community-created badges. Anyone can create a badge. +/// Badge names are unique and cannot be duplicated. +contract TheGuildBadgeRegistry { + /// @notice Representation of a badge. + struct Badge { + bytes32 name; + bytes32 description; + address creator; + } + + /// @notice Emitted when a new badge is created. + event BadgeCreated( + bytes32 indexed name, + bytes32 description, + address indexed creator + ); + + // name => badge + mapping(bytes32 => Badge) private nameToBadge; + // track existence to prevent duplicates + mapping(bytes32 => bool) private nameExists; + + // enumerate badge names + bytes32[] private badgeNames; + + /// @notice Create a new badge with a unique name. + /// @param name The unique badge name (bytes32). + /// @param description The badge description (bytes32). + function createBadge(bytes32 name, bytes32 description) external { + require(name != bytes32(0), "EMPTY_NAME"); + require(!nameExists[name], "DUPLICATE_NAME"); + + Badge memory badge = Badge({ + name: name, + description: description, + creator: msg.sender + }); + nameToBadge[name] = badge; + nameExists[name] = true; + badgeNames.push(name); + + emit BadgeCreated(name, description, msg.sender); + } + + /// @notice Get a badge by its name. + /// @dev Reverts if the badge does not exist. + function getBadge( + bytes32 name + ) external view returns (bytes32, bytes32, address) { + require(nameExists[name], "NOT_FOUND"); + Badge memory b = nameToBadge[name]; + return (b.name, b.description, b.creator); + } + + /// @notice Get whether a badge name exists. + function exists(bytes32 name) external view returns (bool) { + return nameExists[name]; + } + + /// @notice Total number of badges created. + function totalBadges() external view returns (uint256) { + return badgeNames.length; + } + + /// @notice Get badge name at a specific index for enumeration. + /// @dev Reverts if index is out of bounds. + function badgeNameAt(uint256 index) external view returns (bytes32) { + return badgeNames[index]; + } +} diff --git a/the-guild-smart-contracts/test/Counter.t.sol b/the-guild-smart-contracts/test/Counter.t.sol new file mode 100644 index 0000000..4831910 --- /dev/null +++ b/the-guild-smart-contracts/test/Counter.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test} from "forge-std/Test.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterTest is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + function testFuzz_SetNumber(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } +} diff --git a/the-guild-smart-contracts/test/TheGuildBadgeRegistry.t.sol b/the-guild-smart-contracts/test/TheGuildBadgeRegistry.t.sol new file mode 100644 index 0000000..a521c40 --- /dev/null +++ b/the-guild-smart-contracts/test/TheGuildBadgeRegistry.t.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test} from "forge-std/Test.sol"; +import {TheGuildBadgeRegistry} from "../src/TheGuildBadgeRegistry.sol"; + +contract TheGuildBadgeRegistryTest is Test { + TheGuildBadgeRegistry private registry; + + function setUp() public { + registry = new TheGuildBadgeRegistry(); + } + + function test_CreateBadge_SucceedsAndEmitsEvent() public { + bytes32 name = bytes32("BADGE_ALPHA"); + bytes32 description = bytes32("First badge"); + + vm.expectEmit(true, false, false, true); + emit TheGuildBadgeRegistry.BadgeCreated( + name, + description, + address(this) + ); + + registry.createBadge(name, description); + + (bytes32 rName, bytes32 rDesc, address creator) = registry.getBadge( + name + ); + assertEq(rName, name, "name mismatch"); + assertEq(rDesc, description, "description mismatch"); + assertEq(creator, address(this), "creator mismatch"); + assertTrue(registry.exists(name), "should exist"); + assertEq(registry.totalBadges(), 1); + assertEq(registry.badgeNameAt(0), name); + } + + function test_CreateBadge_RevertOnDuplicate() public { + bytes32 name = bytes32("BADGE_DUP"); + registry.createBadge(name, bytes32("desc")); + + vm.expectRevert(bytes("DUPLICATE_NAME")); + registry.createBadge(name, bytes32("another")); + } + + function test_GetBadge_RevertOnMissing() public { + vm.expectRevert(bytes("NOT_FOUND")); + registry.getBadge(bytes32("MISSING")); + } + + function test_CreateBadge_RevertOnEmptyName() public { + vm.expectRevert(bytes("EMPTY_NAME")); + registry.createBadge(bytes32(0), bytes32("desc")); + } +} From 34d23203c098183fac49f75585f995ee49af6420 Mon Sep 17 00:00:00 2001 From: Antoine Estienne Date: Mon, 8 Sep 2025 18:47:12 +0200 Subject: [PATCH 2/5] add integration instructions --- the-guild-smart-contracts/INTEGRATION.md | 206 +++++++++++++++++++++++ the-guild-smart-contracts/README.md | 4 +- 2 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 the-guild-smart-contracts/INTEGRATION.md diff --git a/the-guild-smart-contracts/INTEGRATION.md b/the-guild-smart-contracts/INTEGRATION.md new file mode 100644 index 0000000..1207b25 --- /dev/null +++ b/the-guild-smart-contracts/INTEGRATION.md @@ -0,0 +1,206 @@ +# EAS Integration (TypeScript + wagmi) + +This guide shows how to create on-chain attestations of TheGuild badges from a frontend using the EAS SDK, following the schema described in `README.md`. + +- Schema: `bytes32 badgeName, bytes32 justification` +- Hardcoded schema id for now: set `SCHEMA_ID` to a placeholder and replace later + +References: +- EAS SDK: [Creating on-chain attestations](https://docs.attest.org/docs/developer-tools/eas-sdk#creating-onchain-attestations) +- EAS SDK + wagmi: [wagmi integration](https://docs.attest.org/docs/developer-tools/sdk-wagmi) + +## Install + +```bash +npm install @ethereum-attestation-service/eas-sdk wagmi viem ethers @tanstack/react-query +``` + +## Choose network and addresses + +```ts +// EAS contracts: +// Mainnet: 0xA1207F3BBa224E2c9c3c6D5aF63D0eb1582Ce587 +// Sepolia: 0xC2679fBD37d54388Ce493F1DB75320D236e1815e +export const EAS_CONTRACT_ADDRESS = '0xC2679fBD37d54388Ce493F1DB75320D236e1815e'; // sepolia default for dev + +// Replace with your real schema id once registered in EAS +export const SCHEMA_ID = + '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'; +``` + +## Encoding helper for the schema + +```ts +import { SchemaEncoder } from '@ethereum-attestation-service/eas-sdk'; + +export const schemaEncoder = new SchemaEncoder( + 'bytes32 badgeName, bytes32 justification' +); + +export function encodeBadgeData( + badgeNameBytes32: `0x${string}`, + justificationBytes32: `0x${string}` +) { + return schemaEncoder.encodeData([ + { name: 'badgeName', value: badgeNameBytes32, type: 'bytes32' }, + { name: 'justification', value: justificationBytes32, type: 'bytes32' }, + ]); +} +``` + +## Converting strings to bytes32 + +For UI inputs (badge name and justification), hash the string to `bytes32` for a fixed length value. + +```ts +import { keccak256, encodePacked } from 'viem'; + +export function stringToBytes32(value: string): `0x${string}` { + return keccak256(encodePacked(['string'], [value])); +} +``` + +## Hook: create a badge attestation (wagmi + ethers + EAS SDK) + +This uses wagmi for connection state, ethers Signer for EAS SDK, and the EAS `attest` flow. + +```ts +import { useAccount } from 'wagmi'; +import { EAS } from '@ethereum-attestation-service/eas-sdk'; +import { BrowserProvider } from 'ethers'; +import { EAS_CONTRACT_ADDRESS, SCHEMA_ID } from './easConfig'; +import { encodeBadgeData, stringToBytes32 } from './easEncoding'; + +export function useCreateBadgeAttestation() { + const { isConnected } = useAccount(); + + const createAttestation = async ( + recipient: `0x${string}`, + badgeName: string, + justification: string + ) => { + if (!isConnected) throw new Error('Wallet not connected'); + + // 1) Get signer via ethers from injected provider (wagmi has already connected the wallet) + const provider = new BrowserProvider((window as any).ethereum); + const signer = await provider.getSigner(); + + // 2) Init EAS with signer + const eas = new EAS(EAS_CONTRACT_ADDRESS); + eas.connect(signer); + + // 3) Encode data + const encodedData = encodeBadgeData( + stringToBytes32(badgeName), + stringToBytes32(justification) + ); + + // 4) Attest + const tx = await eas.attest({ + schema: SCHEMA_ID, + data: { + recipient, + expirationTime: 0n, // no expiration + revocable: true, + refUID: '0x0000000000000000000000000000000000000000000000000000000000000000', + data: encodedData, + value: 0n, + }, + }); + + // 5) Wait for receipt and get the new attestation UID + const receipt = await tx.wait(); + return receipt.uid; + }; + + return { createAttestation }; +} +``` + +Notes: +- We use `ethers` Signer because EAS SDK expects an ethers-compatible signer/provider in its official examples. +- wagmi manages connection; the injected provider from the connected wallet is available via `window.ethereum`. + +## Example component + +```tsx +import { useState } from 'react'; +import { useAccount, useConnect } from 'wagmi'; +import { useCreateBadgeAttestation } from './useCreateBadgeAttestation'; + +export function BadgeAttestationForm() { + const { isConnected } = useAccount(); + const { connect, connectors } = useConnect(); + const { createAttestation } = useCreateBadgeAttestation(); + + const [recipient, setRecipient] = useState('' as `0x${string}`); + const [badgeName, setBadgeName] = useState(''); + const [justification, setJustification] = useState(''); + const [txUid, setTxUid] = useState(null); + const [loading, setLoading] = useState(false); + + const onSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!isConnected) return connect({ connector: connectors[0] }); + try { + setLoading(true); + const uid = await createAttestation(recipient, badgeName, justification); + setTxUid(uid); + } catch (err) { + console.error(err); + alert('Failed to create attestation'); + } finally { + setLoading(false); + } + }; + + return ( +
+ setRecipient(e.target.value as `0x${string}`)} + /> + setBadgeName(e.target.value)} + /> + setJustification(e.target.value)} + /> + + {txUid &&

Created attestation UID: {txUid}

} +
+ ); +} +``` + +## Reading an attestation + +```ts +import { EAS } from '@ethereum-attestation-service/eas-sdk'; +import { BrowserProvider } from 'ethers'; +import { EAS_CONTRACT_ADDRESS } from './easConfig'; + +export async function getAttestation(uid: `0x${string}`) { + const provider = new BrowserProvider((window as any).ethereum); + const eas = new EAS(EAS_CONTRACT_ADDRESS); + eas.connect(await provider.getSigner()); + return eas.getAttestation(uid); +} +``` + +## Tips + +- Replace `SCHEMA_ID` with the actual schema id you register in EAS. +- Keep the schema definition exactly as specified: `bytes32 badgeName, bytes32 justification`. +- If you prefer using viem’s clients directly, you can still obtain an `ethers` Signer (as shown) solely for interacting with EAS SDK per its docs. + +Links: [EAS SDK attestations](https://docs.attest.org/docs/developer-tools/eas-sdk#creating-onchain-attestations), [EAS + wagmi](https://docs.attest.org/docs/developer-tools/sdk-wagmi) + + diff --git a/the-guild-smart-contracts/README.md b/the-guild-smart-contracts/README.md index 5475eff..9e55465 100644 --- a/the-guild-smart-contracts/README.md +++ b/the-guild-smart-contracts/README.md @@ -10,8 +10,10 @@ We will create a smart contract TheGuildBadgeRegistry that will have a list of b ### Attestations Then, we let users create an attestation of a badge to another user. The attestation can contain an optional justification (link to a project, or text explanation). -To do this, we can just use EAS’ already deployed contracts. First we will register our schema (using their sdk or their UI): "bytes32 badgeName, bytes32 justification". Then, in the front end, we can use their sdk to create attestation from one user to another, referencing our schema id, the unique badge name, and a justification. We can also use EAS Resolver contract to prevent duplicate badges and reward attestations with Activity Token. +To do this, we can just use EAS' already deployed contracts. First we will register our schema (using their sdk or their UI): "bytes32 badgeName, bytes32 justification". Then, in the front end, we can use their sdk to create attestation from one user to another, referencing our schema id, the unique badge name, and a justification. We can also use EAS Resolver contract to prevent duplicate badges and reward attestations with Activity Token. +### Integration +For detailed frontend integration instructions, see [INTEGRATION.md](./INTEGRATION.md). ## Foundry Usage From 338ce9804236648c5068b2152e80c524342f96b4 Mon Sep 17 00:00:00 2001 From: Antoine Estienne Date: Mon, 8 Sep 2025 18:47:56 +0200 Subject: [PATCH 3/5] remove counter --- .../script/Counter.s.sol | 19 --------------- the-guild-smart-contracts/src/Counter.sol | 14 ----------- the-guild-smart-contracts/test/Counter.t.sol | 24 ------------------- 3 files changed, 57 deletions(-) delete mode 100644 the-guild-smart-contracts/script/Counter.s.sol delete mode 100644 the-guild-smart-contracts/src/Counter.sol delete mode 100644 the-guild-smart-contracts/test/Counter.t.sol diff --git a/the-guild-smart-contracts/script/Counter.s.sol b/the-guild-smart-contracts/script/Counter.s.sol deleted file mode 100644 index f01d69c..0000000 --- a/the-guild-smart-contracts/script/Counter.s.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Script} from "forge-std/Script.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterScript is Script { - Counter public counter; - - function setUp() public {} - - function run() public { - vm.startBroadcast(); - - counter = new Counter(); - - vm.stopBroadcast(); - } -} diff --git a/the-guild-smart-contracts/src/Counter.sol b/the-guild-smart-contracts/src/Counter.sol deleted file mode 100644 index aded799..0000000 --- a/the-guild-smart-contracts/src/Counter.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -contract Counter { - uint256 public number; - - function setNumber(uint256 newNumber) public { - number = newNumber; - } - - function increment() public { - number++; - } -} diff --git a/the-guild-smart-contracts/test/Counter.t.sol b/the-guild-smart-contracts/test/Counter.t.sol deleted file mode 100644 index 4831910..0000000 --- a/the-guild-smart-contracts/test/Counter.t.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Test} from "forge-std/Test.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterTest is Test { - Counter public counter; - - function setUp() public { - counter = new Counter(); - counter.setNumber(0); - } - - function test_Increment() public { - counter.increment(); - assertEq(counter.number(), 1); - } - - function testFuzz_SetNumber(uint256 x) public { - counter.setNumber(x); - assertEq(counter.number(), x); - } -} From 129d9d0299f32fd2bf6c79ca0d58457c046578ce Mon Sep 17 00:00:00 2001 From: Antoine Estienne Date: Mon, 8 Sep 2025 18:54:33 +0200 Subject: [PATCH 4/5] add ci --- .github/workflows/ci.yml | 285 ++++++++++++++++++++++----------------- 1 file changed, 163 insertions(+), 122 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7f23eec..5c2f894 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: CI on: push: - branches: [ main, develop ] + branches: [main, develop] pull_request: - branches: [ main, develop ] + branches: [main, develop] env: CARGO_TERM_COLOR: always @@ -14,42 +14,42 @@ jobs: frontend: name: Frontend Tests runs-on: ubuntu-latest - + steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' - cache-dependency-path: frontend/package-lock.json - - - name: Install dependencies - run: | - cd frontend - npm ci - - - name: Run linter - run: | - cd frontend - npm run lint - - - name: Run tests - run: | - cd frontend - npm run test:run - - - name: Build frontend - run: | - cd frontend - npm run build + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: frontend/package-lock.json + + - name: Install dependencies + run: | + cd frontend + npm ci + + - name: Run linter + run: | + cd frontend + npm run lint + + - name: Run tests + run: | + cd frontend + npm run test:run + + - name: Build frontend + run: | + cd frontend + npm run build backend: name: Backend Tests runs-on: ubuntu-latest - + services: postgres: image: postgres:15 @@ -64,57 +64,99 @@ jobs: --health-retries 5 ports: - 5432:5432 - + steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - components: rustfmt, clippy - override: true - - - name: Cache cargo registry - uses: actions/cache@v3 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - backend/target - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - restore-keys: | - ${{ runner.os }}-cargo- - - - name: Install dependencies - run: | - cd backend - cargo build --verbose - - - name: Run clippy - run: | - cd backend - cargo clippy --all-targets --all-features -- -D warnings - - - name: Run rustfmt - run: | - cd backend - cargo fmt --all -- --check - - - name: Run tests - run: | - cd backend - cargo test --verbose - env: - DATABASE_URL: postgres://postgres:postgres@localhost:5432/guild_genesis_test - TEST_DATABASE_URL: postgres://postgres:postgres@localhost:5432/guild_genesis_test + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + components: rustfmt, clippy + override: true + + - name: Cache cargo registry + uses: actions/cache@v3 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + backend/target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + + - name: Install dependencies + run: | + cd backend + cargo build --verbose + + - name: Run clippy + run: | + cd backend + cargo clippy --all-targets --all-features -- -D warnings + + - name: Run rustfmt + run: | + cd backend + cargo fmt --all -- --check + + - name: Run tests + run: | + cd backend + cargo test --verbose + env: + DATABASE_URL: postgres://postgres:postgres@localhost:5432/guild_genesis_test + TEST_DATABASE_URL: postgres://postgres:postgres@localhost:5432/guild_genesis_test + + smart-contracts: + name: Smart Contracts Tests + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + components: forge, cast, anvil, chisel + + - name: Cache Foundry + uses: actions/cache@v3 + with: + path: | + ~/.foundry + the-guild-smart-contracts/cache + the-guild-smart-contracts/out + key: ${{ runner.os }}-foundry-${{ hashFiles('the-guild-smart-contracts/foundry.lock') }} + restore-keys: | + ${{ runner.os }}-foundry- + + - name: Build contracts + run: | + cd the-guild-smart-contracts + forge build + + - name: Run tests + run: | + cd the-guild-smart-contracts + forge test --verbose + + - name: Run tests with gas reporting + run: | + cd the-guild-smart-contracts + forge test --gas-report integration: name: Integration Tests runs-on: ubuntu-latest - needs: [frontend, backend] - + needs: [frontend, backend, smart-contracts] + services: postgres: image: postgres:15 @@ -129,53 +171,52 @@ jobs: --health-retries 5 ports: - 5432:5432 - + steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' - cache-dependency-path: frontend/package-lock.json - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true - - - name: Install dependencies - run: | - cd frontend && npm ci - cd ../backend && cargo build - - - name: Run integration tests - run: | - cd backend - cargo test --test integration_tests --verbose - env: - DATABASE_URL: postgres://postgres:postgres@localhost:5432/guild_genesis_test - TEST_DATABASE_URL: postgres://postgres:postgres@localhost:5432/guild_genesis_test + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: frontend/package-lock.json + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Install dependencies + run: | + cd frontend && npm ci + cd ../backend && cargo build + + - name: Run integration tests + run: | + cd backend + cargo test --test integration_tests --verbose + env: + DATABASE_URL: postgres://postgres:postgres@localhost:5432/guild_genesis_test + TEST_DATABASE_URL: postgres://postgres:postgres@localhost:5432/guild_genesis_test docker: name: Docker Build Test runs-on: ubuntu-latest - needs: [frontend, backend] - + needs: [frontend, backend, smart-contracts] + steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Build and test Docker images - run: | - docker-compose build - docker-compose up -d postgres - sleep 10 - docker-compose up --build --abort-on-container-exit backend frontend + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Build and test Docker images + run: | + docker-compose build + docker-compose up -d postgres + sleep 10 + docker-compose up --build --abort-on-container-exit backend frontend From 157ab7899bffca1bc8e14cd834d50b414fd283a3 Mon Sep 17 00:00:00 2001 From: Antoine Estienne Date: Mon, 8 Sep 2025 18:55:56 +0200 Subject: [PATCH 5/5] fix ci --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5c2f894..1b1bc09 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -145,7 +145,7 @@ jobs: - name: Run tests run: | cd the-guild-smart-contracts - forge test --verbose + forge test --verbosity - name: Run tests with gas reporting run: |