diff --git a/oracle/oracle_server.js b/oracle/oracle_server.js new file mode 100644 index 0000000..29d9369 --- /dev/null +++ b/oracle/oracle_server.js @@ -0,0 +1,13 @@ +const express = require('express'); +const oracleRoutes = require('./routes'); + +const app = express(); +const HOST = process.env.ORACLE_HOST || '127.0.0.1'; +const PORT = Number(process.env.ORACLE_PORT || 3210); + +app.use(express.json()); +app.use('/api', oracleRoutes); + +app.listen(PORT, HOST, () => { + console.log(`OTB Oracle test server listening on http://${HOST}:${PORT}/api`); +}); diff --git a/oracle/routes.js b/oracle/routes.js new file mode 100644 index 0000000..e2974c4 --- /dev/null +++ b/oracle/routes.js @@ -0,0 +1,89 @@ +const express = require('express'); +const { + listAssets, + getPrices, + buildQuote, + getStatus +} = require('./price_engine'); + +const router = express.Router(); + +function sendError(res, status, message, extra = {}) { + return res.status(status).json({ + ok: false, + error: message, + ...extra + }); +} + +router.get('/health', (req, res) => { + try { + return res.json({ + ok: true, + service: 'otb-oracle', + version: '0.1.0', + time_utc: new Date().toISOString(), + status: 'healthy' + }); + } catch (err) { + return sendError(res, 500, 'health_failed', { detail: err.message }); + } +}); + +router.get('/assets', (req, res) => { + try { + return res.json(listAssets()); + } catch (err) { + return sendError(res, 500, 'assets_failed', { detail: err.message }); + } +}); + +router.get('/prices', (req, res) => { + try { + return res.json(getPrices()); + } catch (err) { + return sendError(res, 500, 'prices_failed', { detail: err.message }); + } +}); + +router.get('/price/:pairKey', (req, res) => { + try { + const pairKey = String(req.params.pairKey || '').trim().toUpperCase(); + const prices = getPrices(); + const asset = prices.assets?.[pairKey]; + + if (!asset) { + return sendError(res, 404, 'asset_not_found', { pair_key: pairKey }); + } + + return res.json(asset); + } catch (err) { + return sendError(res, 500, 'price_lookup_failed', { detail: err.message }); + } +}); + +router.get('/quote', (req, res) => { + try { + const fiat = req.query.fiat || 'CAD'; + const amount = req.query.amount; + + if (amount === undefined || amount === null || amount === '') { + return sendError(res, 400, 'missing_amount'); + } + + const quote = buildQuote({ fiat, amount }); + return res.json(quote); + } catch (err) { + return sendError(res, 400, 'quote_failed', { detail: err.message }); + } +}); + +router.get('/status', (req, res) => { + try { + return res.json(getStatus()); + } catch (err) { + return sendError(res, 500, 'status_failed', { detail: err.message }); + } +}); + +module.exports = router;