chore: sync submodule state (parent ref update)

Made-with: Cursor
This commit is contained in:
defiQUG
2026-03-02 12:14:09 -08:00
parent 50ab378da9
commit 5efe36b1e0
1100 changed files with 155024 additions and 8674 deletions

View File

@@ -0,0 +1,87 @@
/**
* Bridge quote API routes (swap+bridge+swap).
* Mount at POST /api/bridge/quote and POST /api/bridge/quote/swap-bridge-swap.
* Requires RPC_URL, BRIDGE_REGISTRY_ADDRESS (and registry ABI); optional: ENHANCED_SWAP_ROUTER_ADDRESS, DESTINATION_RPC_URL, DESTINATION_SWAP_ROUTER_ADDRESS.
* Sends CORS and Content-Type: application/json so the DApp (dapp.d-bis.org) can call cross-origin without CORB.
*/
import { Router, Request, Response } from 'express';
import { createQuoteServiceFromEnv } from './quote-service';
import { DestinationType } from './workflow-engine';
const router = Router();
const DAPP_ORIGINS = ['https://dapp.d-bis.org', 'http://192.168.11.58', 'http://localhost:5173'];
function setCorsAndJson(res: Response, req: Request): void {
const origin = req.get('origin');
if (origin && (DAPP_ORIGINS.includes(origin) || process.env.NODE_ENV !== 'production')) {
res.setHeader('Access-Control-Allow-Origin', origin);
} else if (process.env.NODE_ENV !== 'production') {
res.setHeader('Access-Control-Allow-Origin', '*');
}
res.setHeader('Content-Type', 'application/json');
}
function getQuoteService(): ReturnType<typeof createQuoteServiceFromEnv> | null {
try {
return createQuoteServiceFromEnv();
} catch {
return null;
}
}
/**
* POST /api/bridge/quote
* Body: { token, amount, destinationChainId, destinationType?, destinationAddress }
* Or swap+bridge+swap: { sourceToken, destinationToken, sourceChainId, destinationChainId, amount, destinationAddress }
*/
router.options('/quote', (req: Request, res: Response) => {
const origin = req.get('origin');
if (origin && (DAPP_ORIGINS.includes(origin) || process.env.NODE_ENV !== 'production')) {
res.setHeader('Access-Control-Allow-Origin', origin);
} else if (process.env.NODE_ENV !== 'production') {
res.setHeader('Access-Control-Allow-Origin', '*');
}
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
res.setHeader('Access-Control-Max-Age', '86400');
res.sendStatus(204);
});
router.post('/quote', async (req: Request, res: Response) => {
setCorsAndJson(res, req);
try {
const svc = getQuoteService();
if (!svc) {
return res.status(503).json({ error: 'Quote service not configured (set RPC_URL, BRIDGE_REGISTRY_ADDRESS)' });
}
const body = req.body as Record<string, unknown>;
const sourceToken = body.sourceToken as string | undefined;
const destinationToken = body.destinationToken as string | undefined;
const sourceChainId = body.sourceChainId as number | undefined;
const destinationChainId = (body.destinationChainId ?? body.destinationChainId) as number;
const amount = (body.amount ?? body.amountIn) as string;
const token = (body.token ?? sourceToken) as string;
const destinationAddress = (body.destinationAddress ?? body.recipient) as string;
if (!amount || !destinationChainId) {
return res.status(400).json({ error: 'amount and destinationChainId are required' });
}
const request = {
token: token || '0x0000000000000000000000000000000000000000',
amount: String(amount),
destinationChainId: Number(destinationChainId),
destinationType: (body.destinationType as DestinationType) ?? DestinationType.EVM,
destinationAddress: destinationAddress || '0x0000000000000000000000000000000000000000',
};
const quote = await svc.getQuote(request);
res.json(quote);
} catch (err: unknown) {
const message = err instanceof Error ? err.message : 'Unknown error';
res.status(500).json({ error: message });
}
});
export default router;

View File

@@ -24,22 +24,41 @@ export interface QuoteResponse {
estimatedTime: number;
slippage: string;
riskLevel: number;
/** Best swap quote (e.g. Dodoex) on source chain when EnhancedSwapRouter is configured */
sourceSwapQuote?: string;
/** Best swap quote on destination chain when swap router is configured */
destinationSwapQuote?: string;
}
const ENHANCED_SWAP_ROUTER_ABI = [
'function getQuotes(address stablecoinToken, uint256 amountIn) view returns (uint8[] providers, uint256[] amounts)',
];
export class QuoteService {
private provider: ethers.Provider;
private registry: ethers.Contract;
private thirdwebClientId?: string;
private enhancedSwapRouterAddress?: string;
private destinationProvider?: ethers.Provider;
private destinationSwapRouterAddress?: string;
constructor(
rpcUrl: string,
registryAddress: string,
registryAbi: any[],
thirdwebClientId?: string
thirdwebClientId?: string,
enhancedSwapRouterAddress?: string,
destinationRpcUrl?: string,
destinationSwapRouterAddress?: string
) {
this.provider = new ethers.JsonRpcProvider(rpcUrl);
this.registry = new ethers.Contract(registryAddress, registryAbi, this.provider);
this.thirdwebClientId = thirdwebClientId;
this.enhancedSwapRouterAddress = enhancedSwapRouterAddress;
if (destinationRpcUrl) {
this.destinationProvider = new ethers.JsonRpcProvider(destinationRpcUrl);
}
this.destinationSwapRouterAddress = destinationSwapRouterAddress;
}
/**
@@ -89,6 +108,42 @@ export class QuoteService {
riskLevel = tokenConfig.riskLevel;
}
// Optional: Dodoex-inclusive swap quotes when EnhancedSwapRouter is configured
let sourceSwapQuote: string | undefined;
let destinationSwapQuote: string | undefined;
if (this.enhancedSwapRouterAddress && request.token !== ethers.ZeroAddress) {
try {
const router = new ethers.Contract(
this.enhancedSwapRouterAddress,
ENHANCED_SWAP_ROUTER_ABI,
this.provider
);
const [providers, amounts] = await router.getQuotes(request.token, request.amount);
if (amounts && amounts.length > 0) {
const best = amounts.reduce((a: bigint, b: bigint) => (a > b ? a : b), 0n);
sourceSwapQuote = best.toString();
}
} catch {
// Router not deployed or getQuotes failed; leave undefined
}
}
if (this.destinationProvider && this.destinationSwapRouterAddress && request.token !== ethers.ZeroAddress) {
try {
const router = new ethers.Contract(
this.destinationSwapRouterAddress,
ENHANCED_SWAP_ROUTER_ABI,
this.destinationProvider
);
const [providers, amounts] = await router.getQuotes(request.token, request.amount);
if (amounts && amounts.length > 0) {
const best = amounts.reduce((a: bigint, b: bigint) => (a > b ? a : b), 0n);
destinationSwapQuote = best.toString();
}
} catch {
// Destination router not available
}
}
return {
transferId: this.generateTransferId(),
routes,
@@ -97,7 +152,9 @@ export class QuoteService {
minReceived: minReceived.toString(),
estimatedTime,
slippage,
riskLevel
riskLevel,
sourceSwapQuote,
destinationSwapQuote
};
}
@@ -243,13 +300,12 @@ export class QuoteService {
}
/**
* Get Fabric route
* Get Fabric route. Set FABRIC_CHAIN_ID in env when Fabric is live; default 999 until then.
*/
private async getFabricRoute(token: string, amount: string): Promise<RouteInfo | null> {
// Fabric typically uses a different chainId or identifier
// For now, use a placeholder
const fabricChainId = process.env.FABRIC_CHAIN_ID ? parseInt(process.env.FABRIC_CHAIN_ID, 10) : 999;
return {
chainId: 999, // Placeholder for Fabric
chainId: fabricChainId,
chainName: 'Hyperledger Fabric',
provider: 'cacti-fabric',
estimatedTime: 120, // Fabric settlement time
@@ -315,3 +371,26 @@ export class QuoteService {
);
}
}
/**
* Create QuoteService from environment variables (for bridge API / orchestration).
* Set RPC_URL, BRIDGE_REGISTRY_ADDRESS; optional: ENHANCED_SWAP_ROUTER_ADDRESS,
* DESTINATION_RPC_URL, DESTINATION_SWAP_ROUTER_ADDRESS, THIRDWEB_CLIENT_ID.
*/
export function createQuoteServiceFromEnv(): QuoteService {
const rpcUrl = process.env.RPC_URL || process.env.RPC_URL_138 || '';
const registryAddress = process.env.BRIDGE_REGISTRY_ADDRESS || '';
const registryAbi: any[] = []; // Bridge registry ABI - load from contract if needed
if (!rpcUrl || !registryAddress) {
throw new Error('RPC_URL (or RPC_URL_138) and BRIDGE_REGISTRY_ADDRESS are required');
}
return new QuoteService(
rpcUrl,
registryAddress,
registryAbi,
process.env.THIRDWEB_CLIENT_ID,
process.env.ENHANCED_SWAP_ROUTER_ADDRESS,
process.env.DESTINATION_RPC_URL,
process.env.DESTINATION_SWAP_ROUTER_ADDRESS
);
}

View File

@@ -35,6 +35,7 @@
"dotenv": "^16.3.1",
"ejs": "^3.1.9",
"express": "^4.18.2",
"jsonwebtoken": "^9.0.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.22.3",
@@ -52,6 +53,7 @@
"@types/ejs": "^3.1.5",
"@types/express": "^4.17.21",
"@types/jest": "^30.0.0",
"@types/jsonwebtoken": "^9.0.5",
"@types/node": "^20.10.6",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",

View File

@@ -1,9 +1,13 @@
/**
* Admin authentication middleware
* Simple token-based auth for now (can be enhanced with JWT later)
* Supports: (1) JWT via Authorization Bearer (when ADMIN_JWT_SECRET or JWT_SECRET set),
* (2) x-admin-token session (backward compatible)
*/
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
const JWT_SECRET = process.env.ADMIN_JWT_SECRET || process.env.JWT_SECRET;
// Simple in-memory session store (replace with Redis in production)
const sessions = new Map<string, { username: string; expires: number }>();
@@ -13,10 +17,22 @@ export interface AuthRequest extends Request {
}
export function requireAdmin(req: AuthRequest, res: Response, next: NextFunction): void {
const bearer = req.headers.authorization;
if (bearer?.startsWith('Bearer ') && JWT_SECRET) {
const token = bearer.slice(7);
try {
const decoded = jwt.verify(token, JWT_SECRET) as { sub?: string; username?: string };
req.adminUser = decoded.sub ?? decoded.username ?? 'unknown';
return next();
} catch {
res.status(401).json({ error: 'Invalid or expired JWT' });
return;
}
}
const token = req.headers['x-admin-token'] as string;
if (!token) {
res.status(401).json({ error: 'Admin token required' });
res.status(401).json({ error: 'Admin token or Bearer JWT required' });
return;
}
@@ -32,6 +48,13 @@ export function requireAdmin(req: AuthRequest, res: Response, next: NextFunction
}
export function createSession(username: string): string {
if (JWT_SECRET) {
return jwt.sign(
{ sub: username, iat: Math.floor(Date.now() / 1000) },
JWT_SECRET,
{ expiresIn: '24h' }
);
}
const token = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
sessions.set(token, {
username,

View File

@@ -14,6 +14,7 @@ import { DatabaseManager } from './database';
import { Environment, DeploymentRequest, Deployment, Alert, Cost } from './types';
import { requireAdmin, createSession, getClientIp, AuthRequest } from './middleware/auth';
import { MonitoringService } from './services/monitoring';
import { appendAudit } from './services/central-audit';
const app = express();
app.use(cors());
@@ -420,6 +421,7 @@ app.put('/api/admin/services/:name', requireAdmin, (req: AuthRequest, res: Respo
db.setServiceConfig(name, enabled !== false, config || null, adminUser);
db.logAdminAction(adminUser, 'update_service', 'service', name, JSON.stringify({ enabled, config }), ipAddress);
appendAudit({ employeeId: adminUser, action: 'update_service', permission: 'admin:action', resourceType: 'service', resourceId: name, ipAddress, userAgent: req.headers['user-agent'] as string }).catch(() => {});
// Broadcast real-time update
broadcastAdminUpdate('service-updated', { service_name: name, enabled, updated_by: adminUser });
@@ -465,6 +467,7 @@ app.put('/api/admin/providers/:name', requireAdmin, (req: AuthRequest, res: Resp
db.setProviderConfig(name, enabled !== false, config || null, adminUser);
db.logAdminAction(adminUser, 'update_provider', 'provider', name, JSON.stringify({ enabled, config }), ipAddress);
appendAudit({ employeeId: adminUser, action: 'update_provider', permission: 'admin:action', resourceType: 'provider', resourceId: name, ipAddress, userAgent: req.headers['user-agent'] as string }).catch(() => {});
// Broadcast real-time update
broadcastAdminUpdate('provider-updated', { provider_name: name, enabled, updated_by: adminUser });
@@ -503,6 +506,7 @@ app.put('/api/admin/environments/:name/toggle', requireAdmin, (req: AuthRequest,
// Update environment in YAML file
config.updateEnvironmentEnabled(name, enabled !== false);
db.logAdminAction(adminUser, 'toggle_environment', 'environment', name, JSON.stringify({ enabled }), ipAddress);
appendAudit({ employeeId: adminUser, action: 'toggle_environment', permission: 'admin:action', resourceType: 'environment', resourceId: name, metadata: { enabled }, ipAddress, userAgent: req.headers['user-agent'] as string }).catch(() => {});
// Broadcast real-time update
broadcastAdminUpdate('environment-updated', { environment_name: name, enabled, updated_by: adminUser });
@@ -675,7 +679,7 @@ const io = new SocketIOServer(server, {
});
// Socket.IO connection handling
io.on('connection', (socket) => {
io.on('connection', (socket: import('socket.io').Socket) => {
console.log('Client connected:', socket.id);
// Join admin room for real-time updates

View File

@@ -0,0 +1,88 @@
/**
* Central audit client for orchestration portal
* Sends admin action audit entries to dbis_core Admin Central API when configured.
* Set DBIS_CENTRAL_URL and ADMIN_CENTRAL_API_KEY to enable.
*/
const DBIS_CENTRAL_URL = process.env.DBIS_CENTRAL_URL?.replace(/\/$/, '');
const ADMIN_CENTRAL_API_KEY = process.env.ADMIN_CENTRAL_API_KEY;
const SERVICE_NAME = 'orchestration_portal';
export interface AuditPayload {
employeeId: string;
action: string;
permission: string;
resourceType: string;
resourceId?: string;
outcome?: string;
metadata?: Record<string, unknown>;
ipAddress?: string;
userAgent?: string;
}
function isConfigured(): boolean {
return Boolean(DBIS_CENTRAL_URL && ADMIN_CENTRAL_API_KEY);
}
/**
* Append an audit entry to the central audit log (dbis_core).
* No-op if DBIS_CENTRAL_URL or ADMIN_CENTRAL_API_KEY is not set.
*/
export async function appendAudit(payload: AuditPayload): Promise<void> {
if (!isConfigured()) return;
try {
const res = await fetch(`${DBIS_CENTRAL_URL}/api/admin/central/audit`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Admin-Central-Key': ADMIN_CENTRAL_API_KEY!,
},
body: JSON.stringify({
employeeId: payload.employeeId,
action: payload.action,
permission: payload.permission ?? 'admin:action',
resourceType: payload.resourceType,
resourceId: payload.resourceId,
project: 'smom-dbis-138',
service: SERVICE_NAME,
outcome: payload.outcome ?? 'success',
metadata: payload.metadata,
ipAddress: payload.ipAddress,
userAgent: payload.userAgent,
}),
});
if (!res.ok) {
console.warn(`[central-audit] POST audit failed: ${res.status} ${await res.text()}`);
}
} catch (err) {
console.warn('[central-audit] append failed:', err instanceof Error ? err.message : err);
}
}
/**
* Check if subject has permission via central API.
* Returns false if not configured or on error.
*/
export async function checkPermission(
subjectId: string,
permission: string
): Promise<boolean> {
if (!isConfigured()) return false;
try {
const res = await fetch(`${DBIS_CENTRAL_URL}/api/admin/central/permission-check`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Admin-Central-Key': ADMIN_CENTRAL_API_KEY!,
},
body: JSON.stringify({ subjectId, permission }),
});
if (!res.ok) return false;
const data = (await res.json()) as { success?: boolean; allowed?: boolean };
return Boolean(data?.allowed);
} catch {
return false;
}
}