Add /v1/validators CRUD endpoint#2
Conversation
Agent-Logs-Url: https://github.com/normancomics/BibleFi-API/sessions/18b17ab1-f81c-4bd9-a2cc-48473bf6421b Co-authored-by: normancomics <70556508+normancomics@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Implements a full /v1/validators CRUD API backed by the Supabase validators table, and replaces the stub server entrypoint with a fuller production Express app that mounts the API routes and background jobs.
Changes:
- Added
src/routes/validators.jswith public read + JWT-protected write operations (create/update/deactivate). - Mounted new validators router in
index.jsand expanded the root discovery response. - Updated Supabase schema to add missing
validatorsindexes and added supportingsrc/libs/middleware/jobs for the full API surface.
Reviewed changes
Copilot reviewed 20 out of 21 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
index.js |
New production Express entrypoint; mounts v1 + protocol routes; starts background jobs. |
src/routes/validators.js |
Adds CRUD endpoints for validator management backed by Supabase. |
src/db/schema.sql |
Adds indexes for validators.active and validators.address (plus full schema). |
src/jobs/index.js |
Adds recurring jobs (market refresh, scripture seeding, church verification, validator rewards). |
src/lib/superfluid.js |
Adds Superfluid CFA/GDA helpers and validator reward calculation logic. |
src/lib/auth.js |
Adds JWT auth middleware and signature verification helpers. |
src/lib/supabase.js |
Creates Supabase client or disables DB features when env is missing. |
src/middleware/index.js |
Adds CORS, rate limiting, request logging, and global error handler. |
src/routes/streams.js |
Adds Superfluid stream create/read/delete endpoints. |
src/routes/x402.js |
Adds HTTP 402 payment-required endpoint and payment header verification flow. |
src/routes/market.js |
Adds market data endpoint with Supabase-backed caching and fallbacks. |
src/routes/scripture.js |
Adds scripture semantic-match endpoint via OpenAI embeddings + Supabase RPC. |
src/routes/mcp.js |
Adds MCP JSON-RPC endpoint with basic tool routing. |
src/routes/a2a.js |
Adds A2A endpoint with optional ERC-8004 signature verification. |
src/routes/churches.js |
Adds churches CRUD endpoints wiring into src/lib/churches.js. |
src/lib/churches.js |
Implements church query/create/update/delete and Google Maps verification. |
src/lib/scripture.js |
Implements scripture querying and embedding seeding logic. |
src/lib/openai.js |
OpenAI client wrapper + embedding helper. |
src/lib/defillama.js |
DefiLlama + Fear & Greed fetch helpers. |
.env.example |
Documents required environment variables for Supabase/OpenAI/Viem/Superfluid/etc. |
package-lock.json |
Updates dependency lockfile for the expanded dependency set. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| app.use('/v1/streams', streamsRouter); | ||
| app.use('/v1/validators', validatorsRouter); |
There was a problem hiding this comment.
strictRateLimit is documented as being for expensive endpoints including blockchain calls, but /v1/streams (which triggers on-chain transactions) is mounted without it. Consider applying strictRateLimit to /v1/streams (and any other routes that do on-chain writes) to reduce abuse/DoS risk and unexpected operator gas spend.
| app.use('/v1/streams', streamsRouter); | |
| app.use('/v1/validators', validatorsRouter); | |
| app.use('/v1/streams', strictRateLimit, streamsRouter); | |
| app.use('/v1/validators', strictRateLimit, validatorsRouter); |
| const { data: validator, error } = await supabase | ||
| .from('validators') | ||
| .insert({ | ||
| address: normalizedAddress, | ||
| name: name ?? null, | ||
| active: true, | ||
| accuracy_score: 0, | ||
| pool_units: '0', | ||
| }) | ||
| .select() | ||
| .single(); | ||
|
|
||
| if (error) { | ||
| // Unique constraint violation | ||
| if (error.code === '23505') { | ||
| return res.status(409).json({ | ||
| error: 'Conflict', | ||
| message: `Validator with address ${normalizedAddress} already exists`, | ||
| }); | ||
| } | ||
| throw error; | ||
| } | ||
|
|
||
| return res.status(201).json(validator); | ||
| } catch (err) { |
There was a problem hiding this comment.
PR description mentions on-chain registration support (calling the contract’s registerValidator(address) when a validator is created), but the POST handler only inserts into Supabase and never initiates an on-chain registration. Add the on-chain call (likely via a helper in src/lib/superfluid.js or a dedicated contract module) and decide on the transactional behavior (e.g., only create DB record after tx succeeds, or create as pending and reconcile in a background job).
| # ── Superfluid (Base mainnet) ───────────────────────────────────────────────── | ||
| SUPERFLUID_HOST_ADDRESS=0x4C073B8641AB798b3197b8B1E3bb33bB92D16Aa | ||
| SUPERFLUID_CFA_ADDRESS=0x19ba78B9cDB05A877718841c574325fdB53601bb | ||
| SUPERFLUID_GDA_ADDRESS=0x6DA13Bde224A05a288748d857b9e7DDEffd1dE08 |
There was a problem hiding this comment.
The env file only defines SUPERFLUID_GDA_ADDRESS, but the code needs both a GDA contract address and a GDA pool address. Add a separate variable for the pool (and clarify naming), otherwise validator reward updates will not know which pool to update.
| SUPERFLUID_GDA_ADDRESS=0x6DA13Bde224A05a288748d857b9e7DDEffd1dE08 | |
| SUPERFLUID_GDA_ADDRESS=0x6DA13Bde224A05a288748d857b9e7DDEffd1dE08 # GDA contract address | |
| SUPERFLUID_GDA_POOL_ADDRESS=0x... # GDA pool address used for validator reward updates |
| flowRate: FLOW_RATE, | ||
| flowRateHuman: `${(Number(FLOW_RATE) * 2592000 / 1e6).toFixed(2)} USDC/month`, | ||
| }, |
There was a problem hiding this comment.
flowRateHuman converts a value documented as "wei/sec" using / 1e6, which makes the monthly amount incorrect by ~1e12 if FLOW_RATE is in 18‑decimals (consistent with the comment and the example value). Use 1e18 (and ideally avoid Number(...) for large values by using BigInt math or a decimal library) so the human-readable amount matches the actual flow rate.
| const units = BigInt(Math.round(accuracyScore * 100)); | ||
|
|
||
| try { | ||
| const pool = process.env.SUPERFLUID_GDA_ADDRESS; |
There was a problem hiding this comment.
calculateValidatorRewards() sets pool to process.env.SUPERFLUID_GDA_ADDRESS, but updatePoolMemberUnits() treats pool as the GDA pool address and uses SUPERFLUID_GDA_ADDRESS as the contract address. Passing the GDA contract address as the pool address will cause updateMemberUnits to revert or silently fail reward updates. Introduce a distinct env var for the pool (e.g. SUPERFLUID_GDA_POOL_ADDRESS) and use that here, keeping SUPERFLUID_GDA_ADDRESS as the contract address.
| const pool = process.env.SUPERFLUID_GDA_ADDRESS; | |
| // Pool address is distinct from the GDA contract address. | |
| const pool = process.env.SUPERFLUID_GDA_POOL_ADDRESS; |
|
|
||
| const JWT_SECRET = process.env.JWT_SECRET ?? 'biblefi-dev-secret-change-in-production'; | ||
|
|
There was a problem hiding this comment.
Falling back to a hard-coded JWT secret means auth becomes trivially forgeable if JWT_SECRET is accidentally unset in a real deployment. Prefer failing fast: require JWT_SECRET to be set (at least when NODE_ENV==="production"), or generate/validate a secure secret at startup and refuse to start when missing.
| const JWT_SECRET = process.env.JWT_SECRET ?? 'biblefi-dev-secret-change-in-production'; | |
| import crypto from 'crypto'; | |
| const JWT_SECRET = (() => { | |
| const secret = process.env.JWT_SECRET; | |
| if (typeof secret === 'string' && secret.trim() !== '') { | |
| return secret; | |
| } | |
| if (process.env.NODE_ENV === 'production') { | |
| throw new Error('JWT_SECRET must be set in production'); | |
| } | |
| return crypto.randomBytes(32).toString('hex'); | |
| })(); |
|
@copilot apply changes based on the comments in this thread |
Implements the full validator management API backed by the
validatorsSupabase table, with on-chain registration support via Superfluid GDA pool units managed in background jobs.New:
src/routes/validators.jsGET /v1/validators?active=true|false(defaulttrue),?limit=N(default 50, max 200)GET /v1/validators/:addressPOST /v1/validatorsviem'sisAddress; 409 on duplicatePUT /v1/validators/:addressname/accuracy_score(0–100); stampsupdated_atDELETE /v1/validators/:addressactive = false) — no hard deleteAll routes return
{ error, message }on failure with a[Validators ROUTE_NAME]log prefix, consistent withstreams.js.Updated:
index.jsReplaced the stub entrypoint with the full production version from
railway/code-change-TESn9Aand mounted the new router:Also added
validators: '/v1/validators'to the discovery endpoint response.Updated:
src/db/schema.sqlAdded missing indexes on the
validatorstable:Supporting files
Brought in all
src/files (lib/,middleware/,routes/,jobs/) from therailway/code-change-TESn9Abranch so the full API surface is present on this branch.Original prompt
Context
The
normancomics/BibleFi-APIrepository has an open PR #1 (railway/code-change-TESn9A→main) that implements the full BWTYA API. Thevalidatorstable already exists insrc/db/schema.sqlwith this structure:The background job in
src/jobs/index.jsalready callscalculateValidatorRewards()every 15 minutes to updatepool_unitsandlast_reward_at.The Superfluid GDA pool is managed via
src/lib/superfluid.js. The smart contract has aregisterValidator(address)function that needs to be called on-chain when a new validator is registered.Task: Add
src/routes/validators.jsCreate a full CRUD REST endpoint at
/v1/validatorswith the following routes:GET /v1/validatorsvalidatorstable in Supabase?active=true|false(default:true),?limit=N(default: 50, max: 200){ validators: [...], count: N }messagefieldGET /v1/validators/:addressPOST /v1/validators(protected — requires JWT viarequireAuth){ address: string (EVM address), name?: string }addressis a valid EVM address usingisAddressfromviemvalidatorstable withactive: true,accuracy_score: 0,pool_units: '0'PUT /v1/validators/:address(protected — requires JWT viarequireAuth)nameand/oraccuracy_score{ name?: string, accuracy_score?: number (0-100) }accuracy_scoreis between 0 and 100 if providedupdated_attoNOW()DELETE /v1/validators/:address(protected — requires JWT viarequireAuth)active: false,updated_at: NOW()){ message: 'Validator deactivated', address }Error handling
{ error: string, message: string }with appropriate HTTP status codes[Validators ROUTE_NAME]prefixTask: Update
index.jsMount the new validators router:
Look at the existing
index.json therailway/code-change-TESn9Abranch and add the validators route in the same style as the other routes.Task: Update
src/db/schema.sqlAdd an index on
validators.activeif it doesn't already exist:Append these at the end of the validators section.
Implementation Notes
import/export default) consistent with the rest of the codebasesupabasefrom../lib/supabase.jsrequireAuthfrom../lib/auth.jsisAddressfromviemsrc/routes/streams.jsaddressfield should always be stored and queried in lowercase (.toLowerCase())copilot/validators-endpointmainThe following is the prior conversation context from the user's chat exploration (may be truncated):
User: help me fix errors and continue to build
Assistant: Here's a full picture of the current state of
Biblefi/Biblefi-biblefi-schemas:📊 Current State
What exists on
mainLICENSEREADME.mdVERSIONING.mdschemas/agent_envelope.schema.jsonschemas/church_record.schema.jsonschemas/defi_strategy.schema.jsonschemas/scripture_record.schema.jsonschemas/security_finding.schema.jsonWhat'...
This pull request was created from Copilot chat.
📱 Kick off Copilot coding agent tasks wherever you are with GitHub Mobile, available on iOS and Android.