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
4 changes: 4 additions & 0 deletions packages/nextjs/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@ FIREBASE_PROJECT_ID="buidlguidl-v3"

# Setup api-key for admin
ADMIN_API_KEY="admin-api-key"

# For Grants-SRE grant completion sync
SRE_API_KEY=
SRE_API_URL=
29 changes: 24 additions & 5 deletions packages/nextjs/app/api/grants/[grantId]/review/route.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { NextRequest, NextResponse } from "next/server";
import { EIP712TypedData } from "@safe-global/safe-core-sdk-types";
import { waitUntil } from "@vercel/functions";
import { recoverTypedDataAddress } from "viem";
import { reviewGrant } from "~~/services/database/grants";
import { getGrantById, reviewGrant } from "~~/services/database/grants";
import { findUserByAddress } from "~~/services/database/users";
import { extractBuildId, sendBuildToSRE } from "~~/services/sre";
import { EIP_712_DOMAIN, EIP_712_TYPES__REVIEW_GRANT, EIP_712_TYPES__REVIEW_GRANT_WITH_NOTE } from "~~/utils/eip712";
import { PROPOSAL_STATUS, ProposalStatusType } from "~~/utils/grants";
import { validateSafeSignature } from "~~/utils/safe-signature";
Expand All @@ -24,7 +26,6 @@ export async function POST(req: NextRequest, { params }: { params: { grantId: st
// Validate action is valid
const validActions = Object.values(PROPOSAL_STATUS);
if (!validActions.includes(action)) {
console.error("Invalid action", action);
return NextResponse.json({ error: "Invalid action" }, { status: 400 });
}

Expand Down Expand Up @@ -84,15 +85,13 @@ export async function POST(req: NextRequest, { params }: { params: { grantId: st
}

if (!isValidSignature) {
console.error("Invalid signature", signer);
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

// Only admins can review grants
const signerData = await findUserByAddress(signer);

if (signerData.data?.role !== "admin") {
console.error("Unauthorized", signer);
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

Expand All @@ -105,9 +104,29 @@ export async function POST(req: NextRequest, { params }: { params: { grantId: st
txChainId,
});
} catch (error) {
console.error("Error approving grant", error);
return NextResponse.json({ error: "Error approving grant" }, { status: 500 });
}

// Send build to SRE to mark it as bgGrant
if (action === PROPOSAL_STATUS.COMPLETED) {
// Use waitUntil for background processing
waitUntil(
(async () => {
try {
const grant = await getGrantById(grantId);
const buildId = extractBuildId(grant?.link);
if (buildId) {
await sendBuildToSRE(buildId);
} else {
console.warn("[SRE] Could not derive buildId from link – skipping SRE sync", grantId);
}
} catch (sreError) {
// Log SRE errors but don't fail the main operation
console.error("[SRE] Error notifying SpeedRunEthereum:", sreError);
}
})(),
);
}

return NextResponse.json({ success: true });
}
1 change: 1 addition & 0 deletions packages/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@safe-global/safe-core-sdk-types": "^5.0.3",
"@uniswap/sdk-core": "^4.0.1",
"@uniswap/v2-sdk": "^3.0.1",
"@vercel/functions": "^2.0.0",
"blo": "^1.0.1",
"daisyui": "4.5.0",
"firebase-admin": "^11.11.1",
Expand Down
49 changes: 49 additions & 0 deletions packages/nextjs/services/sre.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
export const extractBuildId = (link: string | undefined | null): string | null => {
if (!link) return null;

try {
const url = new URL(link);
// Only try to extract the buildId from speedrunethereum.com
if (!url.hostname.endsWith("speedrunethereum.com")) {
return null;
}

const parts = url.pathname.split("/").filter(Boolean);
const id = parts.pop();
if (parts[parts.length - 1] !== "builds" || !id) {
return null;
}
return id;
} catch {
return null;
}
};

export const sendBuildToSRE = async (buildId: string): Promise<void> => {
const apiUrl = process.env.SRE_API_URL;
const apiKey = process.env.SRE_API_KEY;

if (!apiUrl || !apiKey) {
console.warn("[SRE] Missing SRE config (SRE_API_URL or SRE_API_KEY); skipping call");
return;
}
if (!buildId) {
console.warn("[SRE] No buildId supplied, nothing to send to SRE");
return;
}

try {
const payload = { buildId, apiKey };
const response = await fetch(apiUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});

if (!response.ok) {
console.error("[SRE] SRE endpoint returned error status:", response.status);
}
} catch (error) {
console.error("[SRE] Failed to send build information to SRE", error);
}
};
22 changes: 22 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2358,6 +2358,7 @@ __metadata:
"@typescript-eslint/eslint-plugin": ^5.39.0
"@uniswap/sdk-core": ^4.0.1
"@uniswap/v2-sdk": ^3.0.1
"@vercel/functions": ^2.0.0
autoprefixer: ^10.4.12
blo: ^1.0.1
daisyui: 4.5.0
Expand Down Expand Up @@ -3701,6 +3702,20 @@ __metadata:
languageName: node
linkType: hard

"@vercel/functions@npm:^2.0.0":
version: 2.2.8
resolution: "@vercel/functions@npm:2.2.8"
dependencies:
"@vercel/oidc": 2.0.0
peerDependencies:
"@aws-sdk/credential-provider-web-identity": "*"
peerDependenciesMeta:
"@aws-sdk/credential-provider-web-identity":
optional: true
checksum: 06e07f83205dc328018e9dcb6c41f3a71ac3ad2d58707748bda8877cf1877044ec2d959b6f50c008b0ef1310c168986023605180dfe108364926bbb15522ba46
languageName: node
linkType: hard

"@vercel/gatsby-plugin-vercel-analytics@npm:1.0.11":
version: 1.0.11
resolution: "@vercel/gatsby-plugin-vercel-analytics@npm:1.0.11"
Expand Down Expand Up @@ -3798,6 +3813,13 @@ __metadata:
languageName: node
linkType: hard

"@vercel/oidc@npm:2.0.0":
version: 2.0.0
resolution: "@vercel/oidc@npm:2.0.0"
checksum: cb93084e1996dd021fff0e3e88ecd7b33e6451a1646279b6d7309188d2bc3c4249031eb8e40af52e4e145065daee086b8d57cf785be4589a285b2a2643baa125
languageName: node
linkType: hard

"@vercel/python@npm:4.0.2":
version: 4.0.2
resolution: "@vercel/python@npm:4.0.2"
Expand Down