Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/deploy-frontend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ jobs:
PUBLIC_BADGE_REGISTRY_ADDRESS: ${{ secrets.PUBLIC_BADGE_REGISTRY_ADDRESS }}
PUBLIC_EAS_CONTRACT_ADDRESS: ${{ secrets.PUBLIC_EAS_CONTRACT_ADDRESS }}
PUBLIC_ACTIVITY_TOKEN_ADDRESS: ${{ secrets.PUBLIC_ACTIVITY_TOKEN_ADDRESS }}
PUBLIC_ATTESTATION_RESOLVER_ADDRESS: ${{ secrets.PUBLIC_ATTESTATION_RESOLVER_ADDRESS }}
PUBLIC_SCHEMA_ID: ${{ secrets.PUBLIC_SCHEMA_ID }}
run: |
DOCKER_BUILDKIT=0 docker build \
Expand All @@ -65,6 +66,7 @@ jobs:
--build-arg PUBLIC_EAS_CONTRACT_ADDRESS="${PUBLIC_EAS_CONTRACT_ADDRESS}" \
--build-arg PUBLIC_ACTIVITY_TOKEN_ADDRESS="${PUBLIC_ACTIVITY_TOKEN_ADDRESS}" \
--build-arg PUBLIC_SCHEMA_ID="${PUBLIC_SCHEMA_ID}" \
--build-arg PUBLIC_ATTESTATION_RESOLVER_ADDRESS="${PUBLIC_ATTESTATION_RESOLVER_ADDRESS}" \

docker push registry.heroku.com/${HEROKU_FRONTEND_APP}/web
if: steps.changes.outputs.frontend == 'true'
Expand Down
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ PUBLIC_API_URL=http://localhost:3001
PUBLIC_BADGE_REGISTRY_ADDRESS=0x...
PUBLIC_EAS_CONTRACT_ADDRESS=0x...
PUBLIC_ACTIVITY_TOKEN_ADDRESS=0x...
PUBLIC_ATTESTATION_RESOLVER_ADDRESS=0x...
PUBLIC_SCHEMA_ID=0x...

# Discord Bot (if using)
Expand Down Expand Up @@ -167,7 +168,7 @@ forge script script/TheGuildBadgeRegistry.s.sol:TheGuildBadgeRegistryScript --rp

## Smart Contracts

The `the-guild-smart-contracts/` directory contains our Solidity smart contracts built with Foundry.
The `the-guild-smart-contracts/` directory contains our Solidity smart contracts built with Foundry. See the dedicated docs for deployment details, addresses, and usage: [the-guild-smart-contracts/README.md](the-guild-smart-contracts/README.md).

### TheGuildBadgeRegistry

Expand Down Expand Up @@ -203,6 +204,33 @@ function badgeNameAt(uint256 index) external view returns (bytes32)
event BadgeCreated(bytes32 indexed name, bytes32 description, address indexed creator)
```

### TheGuildActivityToken (TGA)

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

**Key Points:**
- 18 decimals; symbol `TGA`
- Owner-only `mint(address to, uint256 amount)`

### TheGuildAttestationResolver

An EAS `SchemaResolver` that validates attestations and mints TGA on success.

**Validation and Behavior:**
- Decodes schema data: `bytes32 badgeName, bytes justification`
- Requires `badgeName` to exist in `TheGuildBadgeRegistry`
- Prevents duplicate `(attester, recipient, badgeName)` via a keyed mapping
- Mints `10 * 10^decimals()` TGA to the attester on success

### TheGuildBadgeRanking

Simple community ranking via upvotes on badges.

**Key Functions:**
- `upvoteBadge(bytes32 badgeName)` – One vote per address per badge
- `getUpvotes(bytes32 badgeName)` – Read total votes
- `hasVotedForBadge(bytes32 badgeName, address voter)` – Read if voted

## Features

### V0 (Current)
Expand Down
11 changes: 0 additions & 11 deletions frontend/.env.example

This file was deleted.

2 changes: 2 additions & 0 deletions frontend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ ARG PUBLIC_API_URL
ARG PUBLIC_BADGE_REGISTRY_ADDRESS
ARG PUBLIC_EAS_CONTRACT_ADDRESS
ARG PUBLIC_ACTIVITY_TOKEN_ADDRESS
ARG PUBLIC_ATTESTATION_RESOLVER_ADDRESS
ARG PUBLIC_SCHEMA_ID

# Expose them to the build environment
Expand All @@ -18,6 +19,7 @@ ENV PUBLIC_WALLET_CONNECT_PROJECT_ID=$PUBLIC_WALLET_CONNECT_PROJECT_ID \
PUBLIC_BADGE_REGISTRY_ADDRESS=$PUBLIC_BADGE_REGISTRY_ADDRESS \
PUBLIC_EAS_CONTRACT_ADDRESS=$PUBLIC_EAS_CONTRACT_ADDRESS \
PUBLIC_ACTIVITY_TOKEN_ADDRESS=$PUBLIC_ACTIVITY_TOKEN_ADDRESS \
PUBLIC_ATTESTATION_RESOLVER_ADDRESS=$PUBLIC_ATTESTATION_RESOLVER_ADDRESS \
PUBLIC_SCHEMA_ID=$PUBLIC_SCHEMA_ID
RUN npm run build

Expand Down
3 changes: 3 additions & 0 deletions frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ PUBLIC_WALLET_CONNECT_PROJECT_ID=your_wallet_connect_project_id
PUBLIC_API_URL=http://localhost:3001
PUBLIC_BADGE_REGISTRY_ADDRESS=0x...
PUBLIC_EAS_CONTRACT_ADDRESS=0x...
# ERC20 token used for balances/transfers
PUBLIC_ACTIVITY_TOKEN_ADDRESS=0x...
# EAS resolver used to read attestation list
PUBLIC_ATTESTATION_RESOLVER_ADDRESS=0x...
PUBLIC_SCHEMA_ID=0x...
```

Expand Down
14 changes: 14 additions & 0 deletions frontend/env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# WalletConnect Project ID
PUBLIC_WALLET_CONNECT_PROJECT_ID=your_project_id_here

# Backend API URL
PUBLIC_API_URL=http://localhost:3001

# Contract addresses (values for amoy)
PUBLIC_BADGE_REGISTRY_ADDRESS=0x94f5F12BE60a338D263882a1A49E81ca8A0c30F4
PUBLIC_EAS_CONTRACT_ADDRESS=0xb101275a60d8bfb14529C421899aD7CA1Ae5B5Fc
# ERC20 activity token (TGA)
PUBLIC_ACTIVITY_TOKEN_ADDRESS=0x4649490B118389d0Be8F48b8953eFb235d8CB545
# EAS resolver used for attestation reads
PUBLIC_ATTESTATION_RESOLVER_ADDRESS=0x8B481fB56b133b590348B2B3B1D3Ae2fce0D4324
PUBLIC_SCHEMA_ID=0xbcd7561083784f9b5a1c2b3ddb7aa9db263d43c58f7374cfa4875646824a47de
19 changes: 14 additions & 5 deletions frontend/src/hooks/attestations/use-create-attestation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,23 @@ function stringToBytes32(value: string): `0x${string}` {
return hex as `0x${string}`;
}

function stringToBytes(value: string): `0x${string}` {
const encoder = new TextEncoder();
const bytes = encoder.encode(value);
let hex = "0x";
for (let i = 0; i < bytes.length; i++)
hex += bytes[i].toString(16).padStart(2, "0");
return hex as `0x${string}`;
}

function encodeBadgeData(
badgeName: `0x${string}`,
justification: `0x${string}`
justificationBytes: `0x${string}`
) {
// Encode according to schema: bytes32 badgeName, bytes32 justification
// Encode according to schema: bytes32 badgeName, bytes justification
return encodeAbiParameters(
[{ type: "bytes32" }, { type: "bytes32" }],
[badgeName, justification]
[{ type: "bytes32" }, { type: "bytes" }],
[badgeName, justificationBytes]
);
}

Expand Down Expand Up @@ -62,7 +71,7 @@ export function useCreateAttestation() {
isBusyRef.current = true;
// Convert strings to bytes32
const badgeNameBytes = stringToBytes32(badgeName);
const justificationBytes = stringToBytes32(justification);
const justificationBytes = stringToBytes(justification);

// Encode data according to schema
const encodedData = encodeBadgeData(badgeNameBytes, justificationBytes);
Expand Down
17 changes: 9 additions & 8 deletions frontend/src/hooks/attestations/use-get-attestations.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useMemo } from "react";
import { useReadContract, useReadContracts } from "wagmi";
import { decodeAbiParameters } from "viem";
import { activityTokenAbi } from "@/lib/abis/activityTokenAbi";
import { ACTIVITY_TOKEN_ADDRESS } from "@/lib/constants/blockchainConstants";
import { bytes32ToString } from "@/lib/utils/blockchainUtils";
import { attestationResolverAbi } from "@/lib/abis/attestationResolverAbi";
import { ATTESTATION_RESOLVER_ADDRESS } from "@/lib/constants/blockchainConstants";
import { bytes32ToString, bytesToString } from "@/lib/utils/blockchainUtils";
import type { AttestationItem } from "@/lib/types/attestation";

function tryDecodeBadgeData(data: `0x${string}` | null | undefined): {
Expand All @@ -13,12 +13,12 @@ function tryDecodeBadgeData(data: `0x${string}` | null | undefined): {
if (!data) return { badgeName: "", justification: "" };
try {
const [nameBytes, justificationBytes] = decodeAbiParameters(
[{ type: "bytes32" }, { type: "bytes32" }],
[{ type: "bytes32" }, { type: "bytes" }],
data
) as [`0x${string}`, `0x${string}`];
return {
badgeName: bytes32ToString(nameBytes),
justification: bytes32ToString(justificationBytes),
justification: bytesToString(justificationBytes),
};
} catch {
return { badgeName: "", justification: "" };
Expand All @@ -31,10 +31,11 @@ export function useGetAttestations(): {
error: Error | null;
refetch: () => void;
} {
const address = ACTIVITY_TOKEN_ADDRESS;
// Use resolver contract for attestation enumeration
const address = ATTESTATION_RESOLVER_ADDRESS;

const countQuery = useReadContract({
abi: activityTokenAbi,
abi: attestationResolverAbi,
address,
functionName: "getAttestationCount",
query: { enabled: Boolean(address) },
Expand All @@ -46,7 +47,7 @@ export function useGetAttestations(): {
() =>
count > 0
? Array.from({ length: count }, (_, i) => ({
abi: activityTokenAbi,
abi: attestationResolverAbi,
address,
functionName: "getAttestationAtIndex" as const,
args: [BigInt(i)],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Minimal ABI fragments we need from TheGuildActivityToken / EAS
export const activityTokenAbi = [
// Minimal ABI fragments we need from
export const attestationResolverAbi = [
{
type: "function",
name: "getAttestationCount",
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/lib/constants/blockchainConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ export const EAS_CONTRACT_ADDRESS = (import.meta.env
export const SCHEMA_ID = (import.meta.env.PUBLIC_SCHEMA_ID ||
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef") as `0x${string}`;

// ERC20 TGA token address (for balances/transfers)
export const ACTIVITY_TOKEN_ADDRESS = (import.meta.env
.PUBLIC_ACTIVITY_TOKEN_ADDRESS || "") as Address;

// EAS resolver contract that exposes attestation getters
export const ATTESTATION_RESOLVER_ADDRESS = (import.meta.env
.PUBLIC_ATTESTATION_RESOLVER_ADDRESS || "") as Address;

export const BADGE_REGISTRY_ADDRESS = (import.meta.env
.PUBLIC_BADGE_REGISTRY_ADDRESS || "") as Address;
13 changes: 13 additions & 0 deletions frontend/src/lib/utils/blockchainUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,19 @@ export function bytes32ToString(value: `0x${string}`): string {
}
}

export function bytesToString(value: `0x${string}`): string {
try {
const hex = value.startsWith("0x") ? value.slice(2) : value;
const arr = new Uint8Array(hex.length / 2);
for (let i = 0; i < arr.length; i++) {
arr[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
}
return new TextDecoder().decode(arr);
} catch (_e) {
return "";
}
}

export function stringToBytes32(value: string): `0x${string}` {
// Encode to utf8, pad/truncate to 32 bytes, return as hex
const encoder = new TextEncoder();
Expand Down
7 changes: 3 additions & 4 deletions the-guild-smart-contracts/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@
# Accepts uint value; example values: 1, 123456, etc.
CREATE2_SALT=1

# RPC URL and PRIVATE KEY are typically provided at runtime to forge script
# RPC_URL=
# PRIVATE_KEY=

# EAS addresses for networks not hardcoded in the deploy script.
# Generic fallback for any other network (used if chain isn't matched)
EAS_ADDRESS=
Expand All @@ -18,3 +14,6 @@ BASE_SEPOLIA_URL=https://base-sepolia.therpc.io

# ETHERSCAN KEY
ETHERSCAN_API_KEY=

# For deployment
PRIVATE_KEY=
2 changes: 2 additions & 0 deletions the-guild-smart-contracts/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ docs/

# Dotenv file
.env

lcov.info
53 changes: 33 additions & 20 deletions the-guild-smart-contracts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,25 @@ For detailed frontend integration instructions, see [INTEGRATION.md](./INTEGRATI

### Amoy

Salt: "theguild_v_0.1.0"
Salt: "theguild_v_0.1.2"

TheGuildActivityToken
https://amoy.polygonscan.com/address/0x5db978bc69e54250f577ed343273508baea136cd
https://amoy.polygonscan.com/address/0x4649490B118389d0Be8F48b8953eFb235d8CB545

TheGuildBadgeRegistry
https://amoy.polygonscan.com/address/0xc142ab6b4688b7b81cb4cc8b305f517bba3bfd25
https://amoy.polygonscan.com/address/0x94f5F12BE60a338D263882a1A49E81ca8A0c30F4

TheGuildAttestationResolver
https://amoy.polygonscan.com/address/0x8B481fB56b133b590348B2B3B1D3Ae2fce0D4324

TheGuildBadgeRanking
https://amoy.polygonscan.com/address/0x435576DFA5B151a6A1c8a4B5EdDfB4ceEd5A55aF

EAS Schema ID:
0xb167f07504166f717f2a2710dbcfbfdf8fad6e8c6128c1a7fa80768f61b1d0b2
0xbcd7561083784f9b5a1c2b3ddb7aa9db263d43c58f7374cfa4875646824a47de

### Amoy for dev
Salt: "theguild_v_0.1.1_dev"
Salt: "theguild_v_0.1.3_dev"

TheGuildActivityToken
https://amoy.polygonscan.com/address/0x82eba5400b4e914a9b5e4573867b9a73c488c1ed
Expand All @@ -46,7 +52,7 @@ EAS Schema ID:
0x8ef2fdb896e42534302cc992c2b2daf614ccabf3fc6d78ce15dc35534b8fa481

### Base Sepolia
Salt: "theguild_v_0.1.1"
Salt: "theguild_v_0.1.3"

TheGuildActivityToken
https://amoy.polygonscan.com/address/0xba838e90ca2a84aed0de2119e7e6f53b9174ce42
Expand Down Expand Up @@ -126,31 +132,38 @@ export CREATE2_SALT=123456
forge script script/TheGuildBadgeRegistry.s.sol:TheGuildBadgeRegistryScript --rpc-url <RPC> --private-key <PK> --broadcast
```

### Tokens
### Tokens & Resolver

- `TheGuildActivityToken` (symbol `TGA`) is an ERC20 with standard 18 decimals. The deployer is the owner and can mint. See `src/TheGuildActivityToken.sol`.
- The token also acts as an EAS Resolver: it inherits `SchemaResolver` and mints 10 TGA to the attester address on each successful attestation.
- `TheGuildActivityToken` (symbol `TGA`) is a plain ERC20 with standard 18 decimals. The deployer is the initial owner and can mint. See `src/TheGuildActivityToken.sol`.
- `TheGuildAttestationResolver` is an EAS `SchemaResolver` that mints TGA to the attester on successful attestations and enforces basic validity rules. It takes the global `IEAS`, the deployed `TheGuildActivityToken`, and the deployed `TheGuildBadgeRegistry` in its constructor. See `src/TheGuildAttestationResolver.sol`.

#### EAS Resolver behavior (TheGuildActivityToken)
#### EAS Resolver behavior (TheGuildAttestationResolver)

- Inherits `SchemaResolver` and implements:
- `onAttest(attestation, value)`: mints `10 * 10^decimals()` to `attestation.attester` and returns `true`.
- `onAttest(attestation, value)`:
- Decodes schema data `"bytes32 badgeName, bytes justification"`.
- Requires `badgeName` exists in `TheGuildBadgeRegistry`.
- Rejects duplicate attestations for the same `(attester, recipient, badgeName)` using a single-slot `keccak256(attester, recipient, badgeName)` mapping.
- If valid, mints `10 * 10^decimals()` to `attestation.attester` and returns `true`.
- `onRevoke(...)`: no-op, returns `true`.
- Constructor requires the global `IEAS` address. Deployment script `script/TheGuildActivityToken.s.sol` auto-selects the EAS address by `chainid` (Base/Optimism/Arbitrum/Polygon, plus testnets) or falls back to `EAS_ADDRESS` env var.
- To use it as a resolver, set this contract address as the `resolver` when registering your EAS Schema. When EAS processes an attestation for that schema, it will call `attest()` on the resolver, which delegates to `onAttest`.
- Learn more about EAS resolvers in the official docs: [Resolver Contracts](https://docs.attest.org/docs/core--concepts/resolver-contracts).
- Deployment wiring:
- Deploy `TheGuildActivityToken`.
- Deploy `TheGuildBadgeRegistry` (if not already deployed).
- Deploy `TheGuildAttestationResolver` with `(IEAS, token, badgeRegistry)`.
- Transfer ownership of the token to the resolver so it can mint: `token.transferOwnership(resolver)`.
- Register your EAS Schema with `resolver` set to the resolver address (not the token!). When EAS processes an attestation for that schema, it calls the resolver which validates and mints tokens.
- Learn more about EAS resolvers: [Resolver Contracts](https://docs.attest.org/docs/core--concepts/resolver-contracts).

Quick steps:

1. Deploy TGA (resolver):
- Uses `IEAS` in constructor; see the deploy script for per-chain EAS addresses.
2. Register your schema in EAS with `resolver` set to the deployed TGA address.
3. Create attestations against that schema. Each attestation mints 10 TGA to the attester automatically.
1. Deploy TGA, Badge Registry, and the Resolver; transfer token ownership to the resolver.
2. Register your schema in EAS with `resolver` set to the resolver address.
3. Create attestations against that schema. Each valid attestation mints 10 TGA to the attester automatically; unknown badges or duplicates are rejected.

Deploy:
Deploy (example):

```shell
forge script script/TheGuildActivityToken.s.sol:TheGuildActivityTokenScript \
forge script script/TheGuildAttestationResolver.s.sol:TheGuildActivityTokenScript \
--rpc-url <your_rpc_url> \
--private-key <your_private_key> \
--broadcast
Expand Down

Large diffs are not rendered by default.

24 changes: 22 additions & 2 deletions the-guild-smart-contracts/deployAmoy.sh
Original file line number Diff line number Diff line change
@@ -1,2 +1,22 @@
source .env
forge script --chain amoy script/FullDeploymentScript.s.sol:FullDeploymentScript --rpc-url $AMOY_RPC_URL --broadcast --verify -vvvv --interactives 1
#!/usr/bin/env bash
set -euo pipefail

if [[ -f .env ]]; then
# shellcheck disable=SC1091
source .env
fi

if [[ -z "${AMOY_RPC_URL:-}" ]]; then
echo "AMOY_RPC_URL env var is required" >&2
exit 1
fi

if [[ -z "${PRIVATE_KEY:-}" ]]; then
echo "PRIVATE_KEY env var is required for non-interactive deployment" >&2
exit 1
fi

forge script --chain amoy script/FullDeploymentScript.s.sol:FullDeploymentScript \
--rpc-url "$AMOY_RPC_URL" \
--private-key "$PRIVATE_KEY" \
--broadcast --verify -vvvv
Loading
Loading