diff --git a/backend/src/routes/allowlist.ts b/backend/src/routes/allowlist.ts index 1c1ba91..0f0a94f 100644 --- a/backend/src/routes/allowlist.ts +++ b/backend/src/routes/allowlist.ts @@ -1,102 +1,42 @@ import { Router } from 'express'; import * as StellarSdk from '@stellar/stellar-sdk'; -import { contractQuery, adminInvoke } from '../lib/stellar.js'; +import { stellarService } from '../lib/stellar.js'; import { asyncHandler } from '../lib/asyncHandler.js'; import { requireAdminKey } from '../middleware/adminAuth.js'; +import { z } from 'zod'; export const allowlistRouter = Router(); +const AddressSchema = z.object({ address: z.string().length(56) }); -allowlistRouter.get('/', asyncHandler(async (req, res) => { - const page = Math.max(1, parseInt((req.query.page as string) ?? '1', 10)); - const limit = Math.min(200, Math.max(1, parseInt((req.query.limit as string) ?? '50', 10))); - const raw = await contractQuery('get_allowlist', []); - const all: string[] = (StellarSdk.scValToNative(raw) as string[]) ?? []; - const start = (page - 1) * limit; - const data = all.slice(start, start + limit); - return res.json({ data, total: all.length, page, limit }); +/** GET /api/allowlist — get all allowlisted addresses */ +allowlistRouter.get('/', asyncHandler(async (_req, res) => { + const result = await stellarService.query('get_allowlist', []); + res.json({ addresses: StellarSdk.scValToNative(result) }); })); -allowlistRouter.post('/', asyncHandler(async (req, res) => { - const { address } = req.body as { address?: string }; - if (!address || typeof address !== 'string') { - return res.status(400).json({ error: 'address is required', code: 'VALIDATION_ERROR' }); - } +/** GET /api/allowlist/:address — check if a single address is allowlisted */ +allowlistRouter.get('/:address', asyncHandler(async (req, res) => { + const address = req.params.address; + + // Validate address is a valid Stellar public key try { StellarSdk.StrKey.decodeEd25519PublicKey(address); } catch { - return res.status(400).json({ error: 'Invalid Stellar address', code: 'VALIDATION_ERROR' }); - } - const hash = await adminInvoke('add_to_allowlist', [ - StellarSdk.nativeToScVal(address, { type: 'address' }), - ]); - return res.json({ hash }); -})); - -allowlistRouter.delete('/', asyncHandler(async (req, res) => { - const { address } = req.body as { address?: string }; - if (!address || typeof address !== 'string') { - return res.status(400).json({ error: 'address is required', code: 'VALIDATION_ERROR' }); - } - const hash = await adminInvoke('remove_from_allowlist', [ - StellarSdk.nativeToScVal(address, { type: 'address' }), - ]); - return res.json({ hash }); -})); - -/** - * POST /api/allowlist/bulk — add multiple addresses in a single request. - * Accepts up to 50 addresses; validates each as a Stellar Ed25519 public key. - * Returns per-address results; partial success is allowed. - * - * Closes #464. - */ -allowlistRouter.post('/bulk', requireAdminKey, asyncHandler(async (req, res) => { - const { addresses } = req.body as { addresses?: unknown }; - - if (!Array.isArray(addresses) || addresses.length === 0) { - return res.status(400).json({ error: 'addresses must be a non-empty array', code: 'VALIDATION_ERROR' }); - } - if (addresses.length > 50) { - return res.status(400).json({ error: 'Maximum 50 addresses per request', code: 'VALIDATION_ERROR' }); + return res.status(400).json({ + error: 'Invalid Stellar address', + code: 'VALIDATION_ERROR' + }); } - const results: Array<{ address: string; hash?: string; error?: string }> = []; - - for (const addr of addresses) { - if (typeof addr !== 'string') { - results.push({ address: String(addr), error: 'Invalid address type' }); - continue; - } - try { - StellarSdk.StrKey.decodeEd25519PublicKey(addr); - } catch { - results.push({ address: addr, error: 'Invalid Stellar public key' }); - continue; - } - try { - const hash = await adminInvoke('add_to_allowlist', [ - StellarSdk.nativeToScVal(addr, { type: 'address' }), - ]); - results.push({ address: addr, hash: hash as string }); - } catch (err: any) { - results.push({ address: addr, error: err.message }); - } - } - - return res.json({ results }); -import { stellarService } from '../lib/stellar.js'; -import { asyncHandler } from '../lib/asyncHandler.js'; -import { requireAdminKey } from '../middleware/adminAuth.js'; -import { z } from 'zod'; - -export const allowlistRouter = Router(); -const AddressSchema = z.object({ address: z.string().length(56) }); - -allowlistRouter.get('/', asyncHandler(async (_req, res) => { + // Query the allowlist and check membership const result = await stellarService.query('get_allowlist', []); - res.json({ addresses: StellarSdk.scValToNative(result) }); + const allowlist: string[] = (StellarSdk.scValToNative(result) as string[]) ?? []; + const allowed = allowlist.includes(address); + + return res.status(200).json({ address, allowed }); })); +/** POST /api/allowlist — add address to allowlist */ allowlistRouter.post('/', requireAdminKey, asyncHandler(async (req, res) => { const { address } = AddressSchema.parse(req.body); const hash = await stellarService.invoke('allowlist_add', [ @@ -105,6 +45,7 @@ allowlistRouter.post('/', requireAdminKey, asyncHandler(async (req, res) => { res.json({ hash }); })); +/** DELETE /api/allowlist/:address — remove address from allowlist */ allowlistRouter.delete('/:address', requireAdminKey, asyncHandler(async (req, res) => { const hash = await stellarService.invoke('allowlist_remove', [ StellarSdk.nativeToScVal(req.params.address, { type: 'address' }),