Skip to content
Draft
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
8 changes: 7 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,10 @@ OPENAI_VECTOR_STORE_ID=vs_...

# GitHub Personal Access Token — required for content sync
# read access
GITHUB_TOKEN=
GITHUB_TOKEN=

STAGING=1
NEXT_PUBLIC_STAGING=1

# PostgreSQL — required for coalition builder
DATABASE_URL=postgres://user:password@localhost:5432/gitcoin_coalitions
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
legacy-peer-deps=true
1 change: 1 addition & 0 deletions contracts/cache/solidity-files-cache.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"_format":"","paths":{"artifacts":"out","build_infos":"out/build-info","sources":"src","tests":"test","scripts":"script","libraries":["lib"]},"files":{"src/DaccCoalitionStaking.sol":{"lastModificationDate":1772872914722,"contentHash":"fab29b59f8cdde69","interfaceReprHash":null,"sourceName":"src/DaccCoalitionStaking.sol","imports":[],"versionRequirement":"^0.8.24","artifacts":{"DaccCoalitionStaking":{"0.8.24":{"default":{"path":"DaccCoalitionStaking.sol/DaccCoalitionStaking.json","build_id":"d0438b8bf5dea4bd"}}}},"seenByCompiler":true}},"builds":["d0438b8bf5dea4bd"],"profiles":{"default":{"solc":{"optimizer":{"enabled":true,"runs":200},"metadata":{"useLiteralContent":false,"bytecodeHash":"ipfs","appendCBOR":true},"outputSelection":{"*":{"*":["abi","evm.bytecode.object","evm.bytecode.sourceMap","evm.bytecode.linkReferences","evm.deployedBytecode.object","evm.deployedBytecode.sourceMap","evm.deployedBytecode.linkReferences","evm.deployedBytecode.immutableReferences","evm.methodIdentifiers","metadata"]}},"evmVersion":"cancun","viaIR":false,"libraries":{}},"vyper":{"evmVersion":"cancun","outputSelection":{"*":{"*":["abi","evm.bytecode","evm.deployedBytecode"]}}}}},"preprocessed":false,"mocks":[]}
7 changes: 7 additions & 0 deletions contracts/foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
solc_version = "0.8.24"
optimizer = true
optimizer_runs = 200

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions contracts/out/build-info/d0438b8bf5dea4bd.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"id":"d0438b8bf5dea4bd","source_id_to_path":{"0":"src/DaccCoalitionStaking.sol"},"language":"Solidity"}
134 changes: 134 additions & 0 deletions contracts/src/DaccCoalitionStaking.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

/// @title d/acc Coalition Staking
/// @notice Stake ETH to signal interest in d/acc domains.
/// Users can withdraw their own stake at any time before the operator deploys funds.
/// The operator (0x00De4B13153673BCAE2616b67bf822500d325Fc3) can withdraw a domain's
/// pooled funds to a wallet of their choice once it reaches 1 ETH.
/// @dev Intentionally simple — no upgradability, no external calls except ETH transfers.
contract DaccCoalitionStaking {
// ── State ────────────────────────────────────────────────────────────
address public immutable operator;
uint256 public constant OPERATOR_THRESHOLD = 1 ether;

/// @dev domainHash => staker => amount
mapping(bytes32 => mapping(address => uint256)) public stakes;

/// @dev domainHash => total staked ETH
mapping(bytes32 => uint256) public domainTotals;

/// @dev domainHash => true once operator has withdrawn (funds deployed)
mapping(bytes32 => bool) public domainDeployed;

// ── Events ───────────────────────────────────────────────────────────
event Staked(
bytes32 indexed domainHash,
string domainId,
address indexed staker,
uint256 amount
);

event Withdrawn(
bytes32 indexed domainHash,
string domainId,
address indexed staker,
uint256 amount
);

event OperatorWithdrawn(
bytes32 indexed domainHash,
string domainId,
address indexed to,
uint256 amount
);

// ── Errors ───────────────────────────────────────────────────────────
error ZeroAmount();
error InsufficientStake();
error DomainAlreadyDeployed();
error BelowThreshold();
error NotOperator();
error TransferFailed();

// ── Constructor ──────────────────────────────────────────────────────
constructor(address _operator) {
operator = _operator;
}

// ── Public functions ─────────────────────────────────────────────────

/// @notice Stake ETH toward a domain. Marks your interest with skin in the game.
/// @param domainId Human-readable domain identifier (e.g. "zero-knowledge-systems")
function stake(string calldata domainId) external payable {
if (msg.value == 0) revert ZeroAmount();
bytes32 hash = _hash(domainId);
if (domainDeployed[hash]) revert DomainAlreadyDeployed();

stakes[hash][msg.sender] += msg.value;
domainTotals[hash] += msg.value;

emit Staked(hash, domainId, msg.sender, msg.value);
}

/// @notice Withdraw your own stake from a domain. Only works before operator deploys.
/// @param domainId The domain to withdraw from
/// @param amount Wei to withdraw
function withdraw(string calldata domainId, uint256 amount) external {
if (amount == 0) revert ZeroAmount();
bytes32 hash = _hash(domainId);
if (domainDeployed[hash]) revert DomainAlreadyDeployed();
if (stakes[hash][msg.sender] < amount) revert InsufficientStake();

stakes[hash][msg.sender] -= amount;
domainTotals[hash] -= amount;

(bool ok, ) = msg.sender.call{value: amount}("");
if (!ok) revert TransferFailed();

emit Withdrawn(hash, domainId, msg.sender, amount);
}

/// @notice Operator withdraws a domain's pooled funds once threshold is met.
/// This marks the domain as "deployed" — individual withdrawals are no longer possible.
/// @param domainId The domain to deploy
/// @param to Destination wallet for the pooled funds
function operatorWithdraw(string calldata domainId, address to) external {
if (msg.sender != operator) revert NotOperator();
bytes32 hash = _hash(domainId);
if (domainDeployed[hash]) revert DomainAlreadyDeployed();

uint256 total = domainTotals[hash];
if (total < OPERATOR_THRESHOLD) revert BelowThreshold();

domainDeployed[hash] = true;

(bool ok, ) = to.call{value: total}("");
if (!ok) revert TransferFailed();

emit OperatorWithdrawn(hash, domainId, to, total);
}

// ── View functions ───────────────────────────────────────────────────

/// @notice Get a user's stake in a domain
function getStake(string calldata domainId, address staker) external view returns (uint256) {
return stakes[_hash(domainId)][staker];
}

/// @notice Get total staked in a domain
function getDomainTotal(string calldata domainId) external view returns (uint256) {
return domainTotals[_hash(domainId)];
}

/// @notice Check if a domain's funds have been deployed by the operator
function isDomainDeployed(string calldata domainId) external view returns (bool) {
return domainDeployed[_hash(domainId)];
}

// ── Internal ─────────────────────────────────────────────────────────

function _hash(string calldata domainId) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(domainId));
}
}
28 changes: 11 additions & 17 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,21 @@ import type { NextConfig } from "next";

const nextConfig: NextConfig = {
images: {
domains: ["images.unsplash.com"],
remotePatterns: [{ hostname: "images.unsplash.com" }],
},

// Skip type-checking during build — run tsc separately in CI/lint
typescript: { ignoreBuildErrors: true },

// Prevent large packages from being bundled into serverless functions
serverExternalPackages: ["three", "@react-three/fiber", "@react-three/drei"],
serverExternalPackages: ["three", "@react-three/fiber", "@react-three/drei", "pg"],

experimental: {
// outputFileTracingExcludes is valid but missing from the TS types
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...(({
outputFileTracingExcludes: {
// Exclude the banner images and three.js from all serverless function
// bundles — they are served as static files and don't need to be
// bundled into functions like opengraph-image routes
"**": [
"public/content-images/**",
"node_modules/three/**",
"node_modules/@react-three/**",
],
},
}) as any),
outputFileTracingExcludes: {
"**": [
"public/content-images/**",
"node_modules/three/**",
"node_modules/@react-three/**",
],
},

async redirects() {
Expand Down
Loading