Skip to content

Add /v1/validators CRUD endpoint#2

Merged
normancomics merged 2 commits into
mainfrom
copilot/update-validator-rewards-calculation
Apr 23, 2026
Merged

Add /v1/validators CRUD endpoint#2
normancomics merged 2 commits into
mainfrom
copilot/update-validator-rewards-calculation

Conversation

Copilot AI commented Mar 29, 2026

Copy link
Copy Markdown
Contributor

Implements the full validator management API backed by the validators Supabase table, with on-chain registration support via Superfluid GDA pool units managed in background jobs.

New: src/routes/validators.js

Route Auth Behavior
GET /v1/validators public List validators; ?active=true|false (default true), ?limit=N (default 50, max 200)
GET /v1/validators/:address public Lookup by EVM address, normalized to lowercase
POST /v1/validators JWT Register; validates EVM address via viem's isAddress; 409 on duplicate
PUT /v1/validators/:address JWT Update name / accuracy_score (0–100); stamps updated_at
DELETE /v1/validators/:address JWT Soft-deactivate (active = false) — no hard delete

All routes return { error, message } on failure with a [Validators ROUTE_NAME] log prefix, consistent with streams.js.

Updated: index.js

Replaced the stub entrypoint with the full production version from railway/code-change-TESn9A and mounted the new router:

import validatorsRouter from './src/routes/validators.js';
// ...
app.use('/v1/validators', validatorsRouter);

Also added validators: '/v1/validators' to the discovery endpoint response.

Updated: src/db/schema.sql

Added missing indexes on the validators table:

CREATE INDEX IF NOT EXISTS idx_validators_active  ON validators (active);
CREATE INDEX IF NOT EXISTS idx_validators_address ON validators (address);

Supporting files

Brought in all src/ files (lib/, middleware/, routes/, jobs/) from the railway/code-change-TESn9A branch so the full API surface is present on this branch.

Original prompt

Context

The normancomics/BibleFi-API repository has an open PR #1 (railway/code-change-TESn9Amain) that implements the full BWTYA API. The validators table already exists in src/db/schema.sql with this structure:

CREATE TABLE IF NOT EXISTS validators (
  id              UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  address         TEXT NOT NULL UNIQUE,  -- EVM wallet address
  name            TEXT,
  accuracy_score  FLOAT DEFAULT 0,       -- 0–100
  pool_units      TEXT DEFAULT '0',      -- Superfluid GDA units (as string for bigint safety)
  active          BOOLEAN DEFAULT TRUE,
  last_reward_at  TIMESTAMPTZ,
  created_at      TIMESTAMPTZ DEFAULT NOW(),
  updated_at      TIMESTAMPTZ DEFAULT NOW()
);

The background job in src/jobs/index.js already calls calculateValidatorRewards() every 15 minutes to update pool_units and last_reward_at.

The Superfluid GDA pool is managed via src/lib/superfluid.js. The smart contract has a registerValidator(address) function that needs to be called on-chain when a new validator is registered.


Task: Add src/routes/validators.js

Create a full CRUD REST endpoint at /v1/validators with the following routes:

GET /v1/validators

  • List all active validators from the validators table in Supabase
  • Support query params: ?active=true|false (default: true), ?limit=N (default: 50, max: 200)
  • Return: { validators: [...], count: N }
  • No auth required (public read)
  • If Supabase is not configured, return an empty array with a message field

GET /v1/validators/:address

  • Get a single validator by EVM address (case-insensitive)
  • Normalize the address to lowercase for lookup
  • Return the validator record, or 404 if not found
  • No auth required

POST /v1/validators (protected — requires JWT via requireAuth)

  • Register a new validator
  • Body: { address: string (EVM address), name?: string }
  • Validate that address is a valid EVM address using isAddress from viem
  • Insert into validators table with active: true, accuracy_score: 0, pool_units: '0'
  • If a validator with that address already exists (unique constraint violation), return 409 Conflict with a helpful message
  • Return 201 with the created validator record

PUT /v1/validators/:address (protected — requires JWT via requireAuth)

  • Update a validator's name and/or accuracy_score
  • Body: { name?: string, accuracy_score?: number (0-100) }
  • Validate accuracy_score is between 0 and 100 if provided
  • Update updated_at to NOW()
  • Return the updated record, or 404 if not found

DELETE /v1/validators/:address (protected — requires JWT via requireAuth)

  • Soft-deactivate a validator (set active: false, updated_at: NOW())
  • Do NOT hard delete
  • Return { message: 'Validator deactivated', address }
  • Return 404 if not found

Error handling

  • All routes should catch errors and return { error: string, message: string } with appropriate HTTP status codes
  • Log errors with a [Validators ROUTE_NAME] prefix

Task: Update index.js

Mount the new validators router:

import validatorsRouter from './src/routes/validators.js';
// ...
app.use('/v1/validators', validatorsRouter);

Look at the existing index.js on the railway/code-change-TESn9A branch and add the validators route in the same style as the other routes.


Task: Update src/db/schema.sql

Add an index on validators.active if it doesn't already exist:

CREATE INDEX IF NOT EXISTS idx_validators_active ON validators (active);
CREATE INDEX IF NOT EXISTS idx_validators_address ON validators (address);

Append these at the end of the validators section.


Implementation Notes

  • Use ES module syntax (import/export default) consistent with the rest of the codebase
  • Import supabase from ../lib/supabase.js
  • Import requireAuth from ../lib/auth.js
  • Import isAddress from viem
  • Use the same error handling pattern as src/routes/streams.js
  • The address field should always be stored and queried in lowercase (.toLowerCase())
  • Branch name: copilot/validators-endpoint
  • Base the PR against main

The 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 main

File Status
LICENSE ✅ Present
README.md ⚠️ Stub (7 lines — just the repo title + one-liner)
VERSIONING.md ⚠️ Empty (1 byte — literally blank)
schemas/agent_envelope.schema.json ⚠️ Empty (1 byte)
schemas/church_record.schema.json ⚠️ Empty (1 byte)
schemas/defi_strategy.schema.json ⚠️ Empty (1 byte)
schemas/scripture_record.schema.json ⚠️ Empty (1 byte)
schemas/security_finding.schema.json ⚠️ Empty (1 byte)

What'...

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.

Copilot AI changed the title [WIP] Update rewards calculation for validators Add /v1/validators CRUD endpoint Mar 29, 2026
Copilot AI requested a review from normancomics March 29, 2026 05:11
@normancomics normancomics marked this pull request as ready for review April 23, 2026 04:49
@normancomics normancomics requested review from Copilot and removed request for normancomics April 23, 2026 04:49
@normancomics normancomics merged commit ddedd90 into main Apr 23, 2026
@normancomics normancomics deleted the copilot/update-validator-rewards-calculation branch April 23, 2026 04:49

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.js with public read + JWT-protected write operations (create/update/deactivate).
  • Mounted new validators router in index.js and expanded the root discovery response.
  • Updated Supabase schema to add missing validators indexes and added supporting src/ 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.

Comment thread index.js
Comment on lines +70 to +71
app.use('/v1/streams', streamsRouter);
app.use('/v1/validators', validatorsRouter);

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
app.use('/v1/streams', streamsRouter);
app.use('/v1/validators', validatorsRouter);
app.use('/v1/streams', strictRateLimit, streamsRouter);
app.use('/v1/validators', strictRateLimit, validatorsRouter);

Copilot uses AI. Check for mistakes.
Comment thread src/routes/validators.js
Comment on lines +100 to +124
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) {

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Comment thread .env.example
# ── Superfluid (Base mainnet) ─────────────────────────────────────────────────
SUPERFLUID_HOST_ADDRESS=0x4C073B8641AB798b3197b8B1E3bb33bB92D16Aa
SUPERFLUID_CFA_ADDRESS=0x19ba78B9cDB05A877718841c574325fdB53601bb
SUPERFLUID_GDA_ADDRESS=0x6DA13Bde224A05a288748d857b9e7DDEffd1dE08

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
SUPERFLUID_GDA_ADDRESS=0x6DA13Bde224A05a288748d857b9e7DDEffd1dE08
SUPERFLUID_GDA_ADDRESS=0x6DA13Bde224A05a288748d857b9e7DDEffd1dE08 # GDA contract address
SUPERFLUID_GDA_POOL_ADDRESS=0x... # GDA pool address used for validator reward updates

Copilot uses AI. Check for mistakes.
Comment thread src/routes/x402.js
Comment on lines +34 to +36
flowRate: FLOW_RATE,
flowRateHuman: `${(Number(FLOW_RATE) * 2592000 / 1e6).toFixed(2)} USDC/month`,
},

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment thread src/lib/superfluid.js
const units = BigInt(Math.round(accuracyScore * 100));

try {
const pool = process.env.SUPERFLUID_GDA_ADDRESS;

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
const pool = process.env.SUPERFLUID_GDA_ADDRESS;
// Pool address is distinct from the GDA contract address.
const pool = process.env.SUPERFLUID_GDA_POOL_ADDRESS;

Copilot uses AI. Check for mistakes.
Comment thread src/lib/auth.js
Comment on lines +3 to +5

const JWT_SECRET = process.env.JWT_SECRET ?? 'biblefi-dev-secret-change-in-production';

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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 uses AI. Check for mistakes.
@normancomics

Copy link
Copy Markdown
Owner

@copilot apply changes based on the comments in this thread

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants