diff --git a/packages/foundry/contracts/Bounty.sol b/packages/foundry/contracts/Bounty.sol index aca59f7..17a473d 100644 --- a/packages/foundry/contracts/Bounty.sol +++ b/packages/foundry/contracts/Bounty.sol @@ -27,7 +27,17 @@ contract Bounty { Public // plaintext CID (public) } + // Severity levels for triage + enum Severity { + None, + Low, + Medium, + High, + Critical + } + address public owner; + address public triager; // Triager can view reports and assign severity uint256 public amount; string public cid; // IPFS CID for bounty details (title, description, severity) Status public status; @@ -40,6 +50,7 @@ contract Bounty { uint256 stake; SubmissionState state; Visibility visibility; // 0=Private, 1=Public + Severity severity; // Assigned by triager } mapping(address => Submission) private _submissions; // per-wallet single submission @@ -53,13 +64,15 @@ contract Bounty { event StakeRefunded(address indexed researcher, uint256 amount); event StakeSlashed(address indexed researcher, uint256 amount, address indexed receiver); event BountyClosed(uint256 winners, uint256 totalPaid); + event SeveritySet(address indexed researcher, Severity severity); event SubmissionVisibilityChanged(address indexed researcher, Visibility visibility, string cid); - constructor(address _owner, string memory _cid, uint256 _stakeAmount, uint256 _duration) payable { + constructor(address _owner, string memory _cid, uint256 _stakeAmount, uint256 _duration, address _triager) payable { require(msg.value > 0, "Bounty amount cannot be zero"); require(_stakeAmount > 0, "Stake amount must be > 0"); require(_duration > 0, "Duration must be > 0"); owner = _owner; + triager = _triager; cid = _cid; amount = msg.value; status = Status.Open; @@ -199,7 +212,7 @@ contract Bounty { /** * @dev Update a submission's visibility and CID. - * Only bounty owner or the submission's researcher can call it. + * Only bounty owner, triager, or the submission's researcher can call it. * Researcher cannot publish while Pending or while owner is fixing. */ function setSubmissionVisibility(address _researcher, Visibility _visibility, string calldata _newCid) external { @@ -207,8 +220,9 @@ contract Bounty { require(subm.state != SubmissionState.None, "No submission"); bool isOwner = msg.sender == owner; + bool isTriager = msg.sender == triager; bool isResearcher = msg.sender == _researcher; - require(isOwner || isResearcher, "Not allowed"); + require(isOwner || isTriager || isResearcher, "Not allowed"); if (isResearcher) { require(subm.state != SubmissionState.Pending, "Not allowed while Pending"); @@ -218,6 +232,19 @@ contract Bounty { subm.visibility = _visibility; emit SubmissionVisibilityChanged(_researcher, _visibility, _newCid); } + + /** + * @dev Set the severity of a submission. Only the triager can call this. + * @param _researcher The address of the researcher whose submission to update. + * @param _severity The severity level to assign. + */ + function setSeverity(address _researcher, Severity _severity) external { + require(msg.sender == triager, "Only triager can set severity"); + Submission storage subm = _submissions[_researcher]; + require(subm.state != SubmissionState.None, "No submission"); + subm.severity = _severity; + emit SeveritySet(_researcher, _severity); + } /** * @dev Returns the list of submitter addresses. */ @@ -230,8 +257,8 @@ contract Bounty { */ function getSubmission( address _researcher - ) external view returns (string memory, uint256, SubmissionState, Visibility) { + ) external view returns (string memory, uint256, SubmissionState, Visibility, Severity) { Submission storage s = _submissions[_researcher]; - return (s.reportCid, s.stake, s.state, s.visibility); + return (s.reportCid, s.stake, s.state, s.visibility, s.severity); } } diff --git a/packages/foundry/contracts/BountyFactory.sol b/packages/foundry/contracts/BountyFactory.sol index 1dc9223..0a44125 100644 --- a/packages/foundry/contracts/BountyFactory.sol +++ b/packages/foundry/contracts/BountyFactory.sol @@ -16,7 +16,8 @@ contract BountyFactory { string cid, uint256 amount, uint256 stakeAmount, - uint256 duration + uint256 duration, + address triager ); constructor() {} @@ -25,21 +26,23 @@ contract BountyFactory { * @dev Creates and deploys a new Bounty contract, funding it with the sent ETH. * @param _owner The address that will own the new bounty. * @param _cid The IPFS CID for the bounty's metadata. + * @param _triager The address that will triage submissions (can be zero address). * @return The address of the newly created Bounty contract. */ function createBounty( address _owner, string memory _cid, uint256 _stakeAmount, - uint256 _duration + uint256 _duration, + address _triager ) external payable returns (address) { // Forward ETH to new bounty as reward pool - Bounty newBounty = new Bounty{ value: msg.value }(_owner, _cid, _stakeAmount, _duration); + Bounty newBounty = new Bounty{ value: msg.value }(_owner, _cid, _stakeAmount, _duration, _triager); address newBountyAddress = address(newBounty); deployedBounties.push(newBountyAddress); - emit BountyCreated(newBountyAddress, _owner, _cid, msg.value, _stakeAmount, _duration); + emit BountyCreated(newBountyAddress, _owner, _cid, msg.value, _stakeAmount, _duration, _triager); return newBountyAddress; } diff --git a/packages/nextjs/app/blockexplorer/address/[address]/page.tsx b/packages/nextjs/app/blockexplorer/address/[address]/page.tsx index bd22a4e..9608225 100644 --- a/packages/nextjs/app/blockexplorer/address/[address]/page.tsx +++ b/packages/nextjs/app/blockexplorer/address/[address]/page.tsx @@ -2,7 +2,7 @@ import fs from "fs"; import type { Metadata } from "next"; import path from "path"; import { Address } from "viem"; -import { hardhat } from "viem/chains"; +import { anvil } from "viem/chains"; import { AddressComponent } from "~~/app/blockexplorer/_components/AddressComponent"; import deployedContracts from "~~/contracts/deployedContracts"; import { isZeroAddress } from "~~/utils/scaffold-eth/common"; @@ -46,7 +46,7 @@ async function fetchByteCodeAndAssembly(buildInfoDirectory: string, contractPath const getContractData = async (address: Address) => { const contracts = deployedContracts as GenericContractsDeclaration | null; - const chainId = hardhat.id; + const chainId = anvil.id; if (!contracts || !contracts[chainId] || Object.keys(contracts[chainId]).length === 0) { return null; @@ -63,7 +63,7 @@ const getContractData = async (address: Address) => { "..", "..", "..", - "hardhat", + "anvil", "artifacts", "build-info", ); diff --git a/packages/nextjs/app/bounties/[id]/page.tsx b/packages/nextjs/app/bounties/[id]/page.tsx index bb04d17..b7a83ba 100644 --- a/packages/nextjs/app/bounties/[id]/page.tsx +++ b/packages/nextjs/app/bounties/[id]/page.tsx @@ -32,7 +32,7 @@ export default function BountyDetailsPage() { const [description, setDescription] = useState(""); const [contact, setContact] = useState(""); const [stakeEth, setStakeEth] = useState("0.00"); - const [metadata, setMetadata] = useState({ title: "Loading...", description: "Loading...", severity: "Medium" }); + const [metadata, setMetadata] = useState({ title: "Loading...", description: "Loading..." }); const [submitting, setSubmitting] = useState(false); const [submitters, setSubmitters] = useState([]); const [committedAmount, setCommittedAmount] = useState(0n); @@ -49,13 +49,14 @@ export default function BountyDetailsPage() { { address: bountyAddress, abi: bountyABI, functionName: "stakeAmount" }, { address: bountyAddress, abi: bountyABI, functionName: "endTime" }, { address: bountyAddress, abi: bountyABI, functionName: "getSubmitters" }, + { address: bountyAddress, abi: bountyABI, functionName: "triager" }, ], query: { refetchInterval: 5000, }, }); - const [owner, amount, cid, status, stakeAmount, endTimeResult, submittersResult] = bountyData || []; + const [owner, amount, cid, status, stakeAmount, endTimeResult, submittersResult, triagerResult] = bountyData || []; const { isSuccess: isConfirmed } = useWaitForTransactionReceipt({ hash }); @@ -113,7 +114,6 @@ export default function BountyDetailsPage() { setMetadata({ title: meta.title || `Bounty: ${bountyAddress.substring(0, 8)}...`, description: meta.description || "No description provided", - severity: meta.severity || "Medium", }); } else { throw new Error(`Lighthouse gateway returned status ${response.status}`); @@ -311,10 +311,12 @@ export default function BountyDetailsPage() {

Posted by

-
-

Severity

-

{metadata.severity}

-
+ {triagerResult?.result && triagerResult.result !== "0x0000000000000000000000000000000000000000" && ( +
+

Triager

+
+
+ )}

Stake

diff --git a/packages/nextjs/app/bounties/create/page.tsx b/packages/nextjs/app/bounties/create/page.tsx index 1d50ecd..8d24030 100644 --- a/packages/nextjs/app/bounties/create/page.tsx +++ b/packages/nextjs/app/bounties/create/page.tsx @@ -10,7 +10,7 @@ import { CurrencyDollarIcon, DocumentTextIcon, PencilSquareIcon, - ShieldExclamationIcon, + ShieldCheckIcon, UserCircleIcon, } from "@heroicons/react/24/outline"; import MDEditor from "~~/components/MDEditor"; @@ -23,7 +23,7 @@ type BountyForm = { description: string; amount: string; projectAddress: string; - severity: "Low" | "Medium" | "High" | "Critical"; + triagerAddress: string; stake: string; durationDays: string; }; @@ -55,7 +55,7 @@ export default function CreateBountyPage() { description: "", amount: "", projectAddress: connectedAddress || "", - severity: "Medium", + triagerAddress: "", stake: "0.01", durationDays: "7", }); @@ -92,7 +92,6 @@ export default function CreateBountyPage() { JSON.stringify({ title: form.title, description: form.description, - severity: form.severity, }), apiKey, ); @@ -101,7 +100,13 @@ export default function CreateBountyPage() { await writeBountyFactoryAsync({ functionName: "createBounty", - args: [form.projectAddress, cid, parseEther(form.stake), BigInt(durationDaysNum * 24 * 60 * 60)], + args: [ + form.projectAddress, + cid, + parseEther(form.stake), + BigInt(durationDaysNum * 24 * 60 * 60), + form.triagerAddress || "0x0000000000000000000000000000000000000000", + ], value: parseEther(form.amount), }); @@ -176,23 +181,6 @@ export default function CreateBountyPage() { placeholder="7" /> - - } - helperText="How critical is the potential vulnerability?" - > - -
@@ -208,6 +196,18 @@ export default function CreateBountyPage() { /> + } + helperText="This address can view reports and assign severity levels. Leave empty if not needed." + > + setForm({ ...form, triagerAddress: value })} + placeholder="The address that will triage submissions" + /> + +
+
+ + )} + {/* Report Content */}

Report Content

{(isOwner || + isTriager || (connectedAddress && connectedAddress.toLowerCase() === (researcherAddr || "").toLowerCase())) && !reportJson && visibility === 0 && ( diff --git a/packages/nextjs/app/reports/page.tsx b/packages/nextjs/app/reports/page.tsx index bac6369..461bcb0 100644 --- a/packages/nextjs/app/reports/page.tsx +++ b/packages/nextjs/app/reports/page.tsx @@ -21,6 +21,7 @@ type ReportItem = { amount: bigint; stake: bigint; visibility: number; + severity: number; }; // Consistent badge styles @@ -30,10 +31,30 @@ const BADGE_GREEN = `${BADGE_BASE} bg-green-900/30 border-green-700 text-green-4 const BADGE_RED = `${BADGE_BASE} bg-red-900/30 border-red-700 text-red-400`; const BADGE_GRAY = `${BADGE_BASE} bg-gray-800 border-gray-700 text-gray-400`; const BADGE_PRIMARY = `${BADGE_BASE} bg-black border-[var(--color-secondary)]/30 text-[var(--color-secondary)]`; +const BADGE_YELLOW = `${BADGE_BASE} bg-yellow-900/30 border-yellow-700 text-yellow-400`; +const BADGE_ORANGE = `${BADGE_BASE} bg-orange-900/30 border-orange-700 text-orange-400`; + +// Severity levels - must match contract enum +const SeverityLabels = ["None", "Low", "Medium", "High", "Critical"] as const; + +const getSeverityBadgeClass = (severity: number) => { + switch (severity) { + case 1: + return BADGE_GREEN; // Low + case 2: + return BADGE_YELLOW; // Medium + case 3: + return BADGE_ORANGE; // High + case 4: + return BADGE_RED; // Critical + default: + return BADGE_GRAY; // None + } +}; export default function ReportsPage() { const { address: connectedAddress } = useAccount(); - const [activeTab, setActiveTab] = useState<"All" | "Created" | "Submitted">("All"); + const [activeTab, setActiveTab] = useState<"All" | "Created" | "Submitted" | "Triaged">("All"); const { targetNetwork } = useTargetNetwork(); const publicClient = usePublicClient({ chainId: targetNetwork.id }); const [committedMap, setCommittedMap] = useState>(new Map()); @@ -80,12 +101,13 @@ export default function ReportsPage() { watch: true, }); - // Phase 1: read owners/status/amount for all bounties + // Phase 1: read owners/status/amount/triager for all bounties const baseReads = useMemo(() => { return (deployedBounties || []).flatMap(addr => [ { address: addr as `0x${string}`, abi: bountyABI, functionName: "owner" }, { address: addr as `0x${string}`, abi: bountyABI, functionName: "status" }, { address: addr as `0x${string}`, abi: bountyABI, functionName: "amount" }, + { address: addr as `0x${string}`, abi: bountyABI, functionName: "triager" }, ]); }, [deployedBounties]); @@ -94,18 +116,19 @@ export default function ReportsPage() { query: { refetchInterval: 20000 }, }); - // Map all bounties to owner/status/amount + list those owned by the connected user + // Map all bounties to owner/status/amount/triager + list those owned by the connected user const bountyInfo = useMemo(() => { if (!baseData || !deployedBounties) - return [] as { addr: `0x${string}`; owner: string; status: number; amount: bigint }[]; - const all: { addr: `0x${string}`; owner: string; status: number; amount: bigint }[] = []; - for (let i = 0; i < baseData.length; i += 3) { - const idx = i / 3; + return [] as { addr: `0x${string}`; owner: string; status: number; amount: bigint; triager: string }[]; + const all: { addr: `0x${string}`; owner: string; status: number; amount: bigint; triager: string }[] = []; + for (let i = 0; i < baseData.length; i += 4) { + const idx = i / 4; all.push({ addr: deployedBounties[idx] as `0x${string}`, owner: (baseData[i]?.result || "0x0") as string, status: (baseData[i + 1]?.result || 0) as number, amount: (baseData[i + 2]?.result || 0n) as bigint, + triager: (baseData[i + 3]?.result || "0x0000000000000000000000000000000000000000") as string, }); } return all; @@ -113,18 +136,36 @@ export default function ReportsPage() { const ownedBounties: { addr: `0x${string}`; status: number; amount: bigint }[] = useMemo(() => { if (!connectedAddress) return []; - return (bountyInfo as { addr: `0x${string}`; owner: string; status: number; amount: bigint }[]) + return (bountyInfo as { addr: `0x${string}`; owner: string; status: number; amount: bigint; triager: string }[]) .filter(b => b.owner.toLowerCase() === connectedAddress.toLowerCase()) .map(b => ({ addr: b.addr, status: b.status, amount: b.amount })); }, [bountyInfo, connectedAddress]); + // Bounties where user is the triager (but not owner) + const triagedBounties: { addr: `0x${string}`; owner: string; status: number; amount: bigint }[] = useMemo(() => { + if (!connectedAddress) return []; + return (bountyInfo as { addr: `0x${string}`; owner: string; status: number; amount: bigint; triager: string }[]) + .filter( + b => + b.triager.toLowerCase() === connectedAddress.toLowerCase() && + b.triager !== "0x0000000000000000000000000000000000000000", + ) + .map(b => ({ addr: b.addr, owner: b.owner, status: b.status, amount: b.amount })); + }, [bountyInfo, connectedAddress]); + // Phase 2: for owned bounties, fetch submitters const { data: submittersData, isLoading: isLoadingSubmitters } = useReadContracts({ contracts: ownedBounties.map(b => ({ address: b.addr, abi: bountyABI, functionName: "getSubmitters" })), query: { refetchInterval: 20000, enabled: ownedBounties.length > 0 }, }); - // Build list of (bounty, submitter) pairs + // Phase 2b: for triaged bounties, fetch submitters + const { data: triagedSubmittersData, isLoading: isLoadingTriagedSubmitters } = useReadContracts({ + contracts: triagedBounties.map(b => ({ address: b.addr, abi: bountyABI, functionName: "getSubmitters" })), + query: { refetchInterval: 20000, enabled: triagedBounties.length > 0 }, + }); + + // Build list of (bounty, submitter) pairs for owned bounties const pairs = useMemo(() => { const out: { bounty: `0x${string}`; submitter: `0x${string}` }[] = []; if (!submittersData) return out; @@ -136,7 +177,19 @@ export default function ReportsPage() { return out; }, [submittersData, ownedBounties]); - // Phase 3: fetch each submission tuple + // Build list of (bounty, submitter) pairs for triaged bounties + const triagedPairs = useMemo(() => { + const out: { bounty: `0x${string}`; submitter: `0x${string}`; owner: string }[] = []; + if (!triagedSubmittersData) return out; + triagedSubmittersData.forEach((res, i) => { + const b = triagedBounties[i]; + const subs = ((res?.result as string[]) || []) as `0x${string}`[]; + subs.forEach(s => out.push({ bounty: b.addr, submitter: s, owner: b.owner })); + }); + return out; + }, [triagedSubmittersData, triagedBounties]); + + // Phase 3: fetch each submission tuple for owned bounties const { data: submissionTuples, isLoading: isLoadingSubs } = useReadContracts({ contracts: pairs.map(p => ({ address: p.bounty, @@ -147,11 +200,22 @@ export default function ReportsPage() { query: { refetchInterval: 20000, enabled: pairs.length > 0 }, }); + // Phase 3b: fetch each submission tuple for triaged bounties + const { data: triagedSubmissionTuples, isLoading: isLoadingTriagedSubs } = useReadContracts({ + contracts: triagedPairs.map(p => ({ + address: p.bounty, + abi: bountyABI, + functionName: "getSubmission", + args: [p.submitter], + })), + query: { refetchInterval: 20000, enabled: triagedPairs.length > 0 }, + }); + const createdReports: ReportItem[] = useMemo(() => { if (!submissionTuples) return []; const out: ReportItem[] = []; submissionTuples.forEach((res, i) => { - const tuple = res?.result as [string, bigint, number, number] | undefined; + const tuple = res?.result as [string, bigint, number, number, number] | undefined; if (!tuple) return; const { bounty, submitter } = pairs[i]; const owned = ownedBounties.find(b => b.addr === bounty); @@ -160,6 +224,7 @@ export default function ReportsPage() { const stake = tuple[1]; const subState = Number(tuple[2] || 0); const visibility = Number(tuple[3] || 0); + const severity = Number(tuple[4] || 0); if (!reportCid) return; out.push({ bounty, @@ -171,11 +236,44 @@ export default function ReportsPage() { amount: (committedMap.get((bounty as string).toLowerCase()) as bigint | undefined) ?? owned.amount, stake, visibility, + severity, }); }); return out; }, [submissionTuples, pairs, ownedBounties, connectedAddress, committedMap]); + // Reports for bounties where user is the triager + const triagedReports: ReportItem[] = useMemo(() => { + if (!triagedSubmissionTuples) return []; + const out: ReportItem[] = []; + triagedSubmissionTuples.forEach((res, i) => { + const tuple = res?.result as [string, bigint, number, number, number] | undefined; + if (!tuple) return; + const { bounty, submitter, owner } = triagedPairs[i]; + const triaged = triagedBounties.find(b => b.addr === bounty); + if (!triaged) return; + const reportCid = tuple[0]; + const stake = tuple[1]; + const subState = Number(tuple[2] || 0); + const visibility = Number(tuple[3] || 0); + const severity = Number(tuple[4] || 0); + if (!reportCid) return; + out.push({ + bounty, + owner, + researcher: submitter, + reportCid, + bountyStatus: triaged.status, + subState, + amount: (committedMap.get((bounty as string).toLowerCase()) as bigint | undefined) ?? triaged.amount, + stake, + visibility, + severity, + }); + }); + return out; + }, [triagedSubmissionTuples, triagedPairs, triagedBounties, committedMap]); + // Researcher's submissions across all bounties const { data: mySubmissionTuples, isLoading: isLoadingMySubs } = useReadContracts({ contracts: @@ -198,7 +296,7 @@ export default function ReportsPage() { infoMap.set(b.addr, { owner: b.owner, status: b.status, amount: b.amount }), ); mySubmissionTuples.forEach((res, i) => { - const tuple = res?.result as [string, bigint, number, number] | undefined; + const tuple = res?.result as [string, bigint, number, number, number] | undefined; if (!tuple) return; const bounty = deployedBounties[i] as `0x${string}`; const reportCid = tuple[0]; @@ -206,6 +304,7 @@ export default function ReportsPage() { const stake = tuple[1]; const subState = Number(tuple[2] || 0); const visibility = Number(tuple[3] || 0); + const severity = Number(tuple[4] || 0); const info = infoMap.get(bounty); if (!info) return; out.push({ @@ -218,6 +317,7 @@ export default function ReportsPage() { amount: (committedMap.get((bounty as string).toLowerCase()) as bigint | undefined) ?? info.amount, stake, visibility, + severity, }); }); return out; @@ -226,27 +326,32 @@ export default function ReportsPage() { const allReports: ReportItem[] = useMemo(() => { const map = new Map(); createdReports.forEach(r => map.set(`${r.bounty}-${r.researcher}`.toLowerCase(), r)); + triagedReports.forEach(r => map.set(`${r.bounty}-${r.researcher}`.toLowerCase(), r)); submittedReports.forEach(r => map.set(`${r.bounty}-${r.researcher}`.toLowerCase(), r)); return Array.from(map.values()); - }, [createdReports, submittedReports]); + }, [createdReports, triagedReports, submittedReports]); const filtered = useMemo(() => { switch (activeTab) { case "Created": return createdReports; + case "Triaged": + return triagedReports; case "Submitted": return submittedReports; case "All": default: return allReports; } - }, [activeTab, createdReports, submittedReports, allReports]); + }, [activeTab, createdReports, triagedReports, submittedReports, allReports]); const isLoading = isLoadingAddresses || (isLoadingBase && (deployedBounties?.length || 0) > 0) || (isLoadingSubmitters && ownedBounties.length > 0) || + (isLoadingTriagedSubmitters && triagedBounties.length > 0) || (isLoadingSubs && pairs.length > 0) || + (isLoadingTriagedSubs && triagedPairs.length > 0) || (isLoadingMySubs && (deployedBounties?.length || 0) > 0); return ( @@ -287,6 +392,16 @@ export default function ReportsPage() { > My Submissions +
@@ -343,6 +458,11 @@ export default function ReportsPage() { {r.visibility === 1 ? "Public" : "Private"} + {r.severity > 0 && ( + + {SeverityLabels[r.severity]} + + )}
diff --git a/packages/nextjs/contracts/BountyABI.ts b/packages/nextjs/contracts/BountyABI.ts index 00afa42..66f4637 100644 --- a/packages/nextjs/contracts/BountyABI.ts +++ b/packages/nextjs/contracts/BountyABI.ts @@ -10,291 +10,462 @@ export const SubmissionStatus = [ // The ABI can be found in packages/hardhat/generated/artifacts/Bounty.js export const bountyABI = [ - { - inputs: [ - { internalType: "address", name: "_owner", type: "address" }, - { internalType: "string", name: "_cid", type: "string" }, - { internalType: "uint256", name: "_stakeAmount", type: "uint256" }, - { internalType: "uint256", name: "_duration", type: "uint256" }, - ], - stateMutability: "payable", - type: "constructor", - }, - { - anonymous: false, - inputs: [ - { - indexed: false, - internalType: "uint256", - name: "winners", - type: "uint256", - }, - { - indexed: false, - internalType: "uint256", - name: "totalPaid", - type: "uint256", - }, - ], - name: "BountyClosed", - type: "event", - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "address", - name: "researcher", - type: "address", - }, - { - indexed: false, - internalType: "uint256", - name: "amount", - type: "uint256", - }, - ], - name: "FundsReleased", - type: "event", - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "address", - name: "researcher", - type: "address", - }, - { - indexed: false, - internalType: "string", - name: "reportCid", - type: "string", - }, - ], - name: "ReportSubmitted", - type: "event", - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "address", - name: "researcher", - type: "address", - }, - { - indexed: false, - internalType: "uint256", - name: "amount", - type: "uint256", - }, - ], - name: "StakeDeposited", - type: "event", - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "address", - name: "researcher", - type: "address", - }, - { - indexed: false, - internalType: "uint256", - name: "amount", - type: "uint256", - }, - ], - name: "StakeRefunded", - type: "event", - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "address", - name: "researcher", - type: "address", - }, - { - indexed: false, - internalType: "uint256", - name: "amount", - type: "uint256", - }, - { - indexed: true, - internalType: "address", - name: "receiver", - type: "address", - }, - ], - name: "StakeSlashed", - type: "event", - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "address", - name: "researcher", - type: "address", - }, - ], - name: "SubmissionAccepted", - type: "event", - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "address", - name: "researcher", - type: "address", - }, - ], - name: "SubmissionRejected", - type: "event", - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "address", - name: "researcher", - type: "address", - }, - { - indexed: false, - internalType: "enum Bounty.Visibility", - name: "visibility", - type: "uint8", - }, - { indexed: false, internalType: "string", name: "cid", type: "string" }, - ], - name: "SubmissionVisibilityChanged", - type: "event", - }, - { - inputs: [{ internalType: "address", name: "_researcher", type: "address" }], - name: "acceptSubmission", - outputs: [], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [], - name: "amount", - outputs: [{ internalType: "uint256", name: "", type: "uint256" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "cid", - outputs: [{ internalType: "string", name: "", type: "string" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "close", - outputs: [], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [], - name: "closeIfExpired", - outputs: [], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [], - name: "endTime", - outputs: [{ internalType: "uint256", name: "", type: "uint256" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [{ internalType: "address", name: "_researcher", type: "address" }], - name: "getSubmission", - outputs: [ - { internalType: "string", name: "", type: "string" }, - { internalType: "uint256", name: "", type: "uint256" }, - { internalType: "enum Bounty.SubmissionState", name: "", type: "uint8" }, - { internalType: "enum Bounty.Visibility", name: "", type: "uint8" }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "getSubmitters", - outputs: [{ internalType: "address[]", name: "", type: "address[]" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "owner", - outputs: [{ internalType: "address", name: "", type: "address" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [{ internalType: "address", name: "_researcher", type: "address" }], - name: "rejectSubmission", - outputs: [], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [ - { internalType: "address", name: "_researcher", type: "address" }, - { - internalType: "enum Bounty.Visibility", - name: "_visibility", - type: "uint8", - }, - { internalType: "string", name: "_newCid", type: "string" }, - ], - name: "setSubmissionVisibility", - outputs: [], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [], - name: "stakeAmount", - outputs: [{ internalType: "uint256", name: "", type: "uint256" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "status", - outputs: [{ internalType: "enum Bounty.Status", name: "", type: "uint8" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [{ internalType: "string", name: "_reportCid", type: "string" }], - name: "submitReport", - outputs: [], - stateMutability: "payable", - type: "function", - }, -] as const; + { + "type": "constructor", + "inputs": [ + { + "name": "_owner", + "type": "address", + "internalType": "address" + }, + { + "name": "_cid", + "type": "string", + "internalType": "string" + }, + { + "name": "_stakeAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_duration", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_triager", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "acceptSubmission", + "inputs": [ + { + "name": "_researcher", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "amount", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "cid", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "close", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "closeIfExpired", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "endTime", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getSubmission", + "inputs": [ + { + "name": "_researcher", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "uint8", + "internalType": "enum Bounty.SubmissionState" + }, + { + "name": "", + "type": "uint8", + "internalType": "enum Bounty.Visibility" + }, + { + "name": "", + "type": "uint8", + "internalType": "enum Bounty.Severity" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getSubmitters", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address[]", + "internalType": "address[]" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "rejectSubmission", + "inputs": [ + { + "name": "_researcher", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setSeverity", + "inputs": [ + { + "name": "_researcher", + "type": "address", + "internalType": "address" + }, + { + "name": "_severity", + "type": "uint8", + "internalType": "enum Bounty.Severity" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setSubmissionVisibility", + "inputs": [ + { + "name": "_researcher", + "type": "address", + "internalType": "address" + }, + { + "name": "_visibility", + "type": "uint8", + "internalType": "enum Bounty.Visibility" + }, + { + "name": "_newCid", + "type": "string", + "internalType": "string" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "stakeAmount", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "status", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint8", + "internalType": "enum Bounty.Status" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "submitReport", + "inputs": [ + { + "name": "_reportCid", + "type": "string", + "internalType": "string" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "triager", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "event", + "name": "BountyClosed", + "inputs": [ + { + "name": "winners", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "totalPaid", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "FundsReleased", + "inputs": [ + { + "name": "researcher", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ReportSubmitted", + "inputs": [ + { + "name": "researcher", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "reportCid", + "type": "string", + "indexed": false, + "internalType": "string" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "SeveritySet", + "inputs": [ + { + "name": "researcher", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "severity", + "type": "uint8", + "indexed": false, + "internalType": "enum Bounty.Severity" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "StakeDeposited", + "inputs": [ + { + "name": "researcher", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "StakeRefunded", + "inputs": [ + { + "name": "researcher", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "StakeSlashed", + "inputs": [ + { + "name": "researcher", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "receiver", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "SubmissionAccepted", + "inputs": [ + { + "name": "researcher", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "SubmissionRejected", + "inputs": [ + { + "name": "researcher", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "SubmissionVisibilityChanged", + "inputs": [ + { + "name": "researcher", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "visibility", + "type": "uint8", + "indexed": false, + "internalType": "enum Bounty.Visibility" + }, + { + "name": "cid", + "type": "string", + "indexed": false, + "internalType": "string" + } + ], + "anonymous": false + } + ] as const; diff --git a/packages/nextjs/contracts/deployedContracts.ts b/packages/nextjs/contracts/deployedContracts.ts index 4709530..ca04ec4 100644 --- a/packages/nextjs/contracts/deployedContracts.ts +++ b/packages/nextjs/contracts/deployedContracts.ts @@ -7,7 +7,7 @@ import { GenericContractsDeclaration } from "~~/utils/scaffold-eth/contract"; const deployedContracts = { 31337: { BountyFactory: { - address: "0x700b6a60ce7eaaea56f065753d8dcb9653dbad35", + address: "0xe1aa25618fa0c7a1cfdab5d6b456af611873b629", abi: [ { type: "constructor", @@ -38,6 +38,11 @@ const deployedContracts = { type: "uint256", internalType: "uint256", }, + { + name: "_triager", + type: "address", + internalType: "address", + }, ], outputs: [ { @@ -120,12 +125,18 @@ const deployedContracts = { indexed: false, internalType: "uint256", }, + { + name: "triager", + type: "address", + indexed: false, + internalType: "address", + }, ], anonymous: false, }, ], inheritedFunctions: {}, - deployedOnBlock: 1, + deployedOnBlock: 5, }, }, 11155111: { @@ -161,6 +172,11 @@ const deployedContracts = { type: "uint256", internalType: "uint256", }, + { + name: "_triager", + type: "address", + internalType: "address", + }, ], outputs: [ { @@ -243,6 +259,12 @@ const deployedContracts = { indexed: false, internalType: "uint256", }, + { + name: "triager", + type: "address", + indexed: false, + internalType: "address", + }, ], anonymous: false, }, diff --git a/packages/nextjs/scaffold.config.ts b/packages/nextjs/scaffold.config.ts index e12d709..f108729 100644 --- a/packages/nextjs/scaffold.config.ts +++ b/packages/nextjs/scaffold.config.ts @@ -15,7 +15,7 @@ export const DEFAULT_ALCHEMY_API_KEY = "oKxs-03sij-U_N0iOlrSsZFr29-IqbuF"; const scaffoldConfig = { // The networks on which your DApp is live - targetNetworks: [chains.sepolia], + targetNetworks: [chains.sepolia, chains.anvil], // The interval at which your front-end polls the RPC servers for new data (it has no effect if you only target the local network (default is 4000)) pollingInterval: 30000, // This is ours Alchemy's default API key.