feat: wire live Fineract M1 to Central Bank UI and Office 24 journal seed
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m21s
CI/CD Pipeline / Security Scanning (push) Successful in 2m42s
CI/CD Pipeline / Terraform Validation (push) Has been cancelled
CI/CD Pipeline / Kubernetes Validation (push) Has been cancelled
CI/CD Pipeline / Lint and Format (push) Has been cancelled
Deploy ChainID 138 / Deploy ChainID 138 (push) Has been cancelled
Validation / validate-genesis (push) Has been cancelled
Validation / validate-terraform (push) Has been cancelled
Validation / validate-kubernetes (push) Has been cancelled
Validation / validate-smart-contracts (push) Has been cancelled
Validation / validate-security (push) Has been cancelled
Validation / validate-documentation (push) Has been cancelled
Verify Deployment / Verify Deployment (push) Has been cancelled
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m21s
CI/CD Pipeline / Security Scanning (push) Successful in 2m42s
CI/CD Pipeline / Terraform Validation (push) Has been cancelled
CI/CD Pipeline / Kubernetes Validation (push) Has been cancelled
CI/CD Pipeline / Lint and Format (push) Has been cancelled
Deploy ChainID 138 / Deploy ChainID 138 (push) Has been cancelled
Validation / validate-genesis (push) Has been cancelled
Validation / validate-terraform (push) Has been cancelled
Validation / validate-kubernetes (push) Has been cancelled
Validation / validate-smart-contracts (push) Has been cancelled
Validation / validate-security (push) Has been cancelled
Validation / validate-documentation (push) Has been cancelled
Verify Deployment / Verify Deployment (push) Has been cancelled
Add a read-only money-supply endpoint for Office 24 dashboards, pass Fineract env through deploy scripts, and provide a seed script for the opening Dr 1410 / Cr 2100 journal. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useCentralBank } from './useCentralBank';
|
||||
import ChainMatrixPanel from './ChainMatrixPanel';
|
||||
import { SETTLEMENT_MIDDLEWARE_URL, DBIS_EXCHANGE_URL } from '../../config/dex';
|
||||
import { formatUsd } from '../../utils/formatMoney';
|
||||
|
||||
function StatCard({
|
||||
label,
|
||||
@@ -58,19 +59,19 @@ export default function CentralBankDashboard() {
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
|
||||
<StatCard
|
||||
label="M0 Reserve (GL 1050)"
|
||||
value={cb.moneySupply?.M0?.gl1050 ?? '—'}
|
||||
value={formatUsd(cb.moneySupply?.M0?.gl1050)}
|
||||
sub={`Backing ${cb.moneySupply?.M0?.backingRatio ?? 1.2}×`}
|
||||
accent="blue"
|
||||
/>
|
||||
<StatCard
|
||||
label="M1 Circulating (GL 2100)"
|
||||
value={cb.moneySupply?.M1?.circulating ?? '—'}
|
||||
value={formatUsd(cb.moneySupply?.M1?.circulating)}
|
||||
sub="Narrow money · eMoney"
|
||||
accent="green"
|
||||
/>
|
||||
<StatCard
|
||||
label="M2 Broad (GL 2200)"
|
||||
value={cb.moneySupply?.M2?.broadMoney ?? '—'}
|
||||
value={formatUsd(cb.moneySupply?.M2?.broadMoney)}
|
||||
sub="Token load source"
|
||||
accent="gold"
|
||||
/>
|
||||
@@ -82,10 +83,27 @@ export default function CentralBankDashboard() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{cb.moneySupply?.fineractLive === false && (
|
||||
<p className="text-xs text-[#f6465d] mb-6">
|
||||
Fineract GL balances unavailable — check OMNL_FINERACT_* on settlement middleware and seed
|
||||
Office 24 opening journal (Dr 1410 / Cr 2100).
|
||||
</p>
|
||||
)}
|
||||
|
||||
{cb.moneySupply?.asOf && (
|
||||
<p className="text-xs text-[#848e9c] mb-6">
|
||||
Live Fineract · Office {cb.moneySupply.officeId ?? 24} · as of{' '}
|
||||
{cb.moneySupply.asOf.slice(0, 19).replace('T', ' ')} UTC
|
||||
{cb.moneySupply.interoffice?.dueFromHeadOffice
|
||||
? ` · Due from HO (GL 1410): ${formatUsd(cb.moneySupply.interoffice.dueFromHeadOffice)}`
|
||||
: ''}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{!cb.moneySupply && (
|
||||
<p className="text-xs text-[#848e9c] mb-6">
|
||||
Set <code className="text-[#f0b90b]">VITE_OMNL_API_KEY</code> in .env.local to load live M0/M1/M2
|
||||
balances from Fineract.
|
||||
Money supply loads from{' '}
|
||||
<code className="text-[#f0b90b]">/money-supply/public</code> when online bank mode is enabled.
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
||||
@@ -4,11 +4,26 @@ import { DBIS_EXCHANGE_URL, SETTLEMENT_MIDDLEWARE_URL } from '../../config/dex';
|
||||
export type MoneySupply = {
|
||||
asOf?: string;
|
||||
officeId?: number;
|
||||
fineractLive?: boolean;
|
||||
interoffice?: { dueFromHeadOffice?: string; gl1410?: string };
|
||||
M0?: { gl1050?: string; backingRatio?: number };
|
||||
M1?: { circulating?: string; gl2100?: string };
|
||||
M2?: { broadMoney?: string; gl2200?: string; metaFiatPending?: string };
|
||||
};
|
||||
|
||||
async function loadMoneySupply(settlement: string): Promise<MoneySupply | null> {
|
||||
const publicRes = await fetch(`${settlement}/api/v1/settlement/money-supply/public`);
|
||||
if (publicRes.ok) return publicRes.json();
|
||||
|
||||
const apiKey = import.meta.env.VITE_OMNL_API_KEY;
|
||||
if (!apiKey) return null;
|
||||
|
||||
const authRes = await fetch(`${settlement}/api/v1/settlement/money-supply`, {
|
||||
headers: { Authorization: `Bearer ${apiKey}` },
|
||||
});
|
||||
return authRes.ok ? authRes.json() : null;
|
||||
}
|
||||
|
||||
export function useCentralBank() {
|
||||
const [health, setHealth] = useState<Record<string, unknown> | null>(null);
|
||||
const [office, setOffice] = useState<Record<string, unknown> | null>(null);
|
||||
@@ -25,24 +40,18 @@ export function useCentralBank() {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const [h, o, m2, mon] = await Promise.all([
|
||||
const [h, o, m2, mon, ms] = await Promise.all([
|
||||
fetch(`${settlement}/api/v1/settlement/health`).then((r) => r.json()),
|
||||
fetch(`${settlement}/api/v1/settlement/office`).then((r) => r.json()),
|
||||
fetch(`${settlement}/api/v1/settlement/tokens/m2`).then((r) => r.json()),
|
||||
fetch(`${exchange}/api/v1/exchange/swap/monitor?limit=10`).then((r) => r.json()),
|
||||
loadMoneySupply(settlement),
|
||||
]);
|
||||
setHealth(h);
|
||||
setOffice(o);
|
||||
setTokenCount(Array.isArray(m2.tokens) ? m2.tokens.length : 0);
|
||||
setMonitor(mon);
|
||||
|
||||
const apiKey = import.meta.env.VITE_OMNL_API_KEY;
|
||||
if (apiKey) {
|
||||
const ms = await fetch(`${settlement}/api/v1/settlement/money-supply`, {
|
||||
headers: { Authorization: `Bearer ${apiKey}` },
|
||||
});
|
||||
if (ms.ok) setMoneySupply(await ms.json());
|
||||
}
|
||||
setMoneySupply(ms);
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : 'Failed to load central bank data');
|
||||
} finally {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useOffice24 } from './useOffice24';
|
||||
import { formatUsd } from '../../utils/formatMoney';
|
||||
|
||||
export default function Office24Dashboard() {
|
||||
const { office, tokens, health, loading, refresh } = useOffice24();
|
||||
const { office, tokens, health, moneySupply, loading, refresh } = useOffice24();
|
||||
const profile = office as {
|
||||
officeId?: number;
|
||||
externalId?: string;
|
||||
@@ -34,14 +35,22 @@ export default function Office24Dashboard() {
|
||||
</Link>
|
||||
</header>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8">
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
|
||||
<div className="o24-card">
|
||||
<p className="text-xs text-[#848e9c] uppercase">Office ID</p>
|
||||
<p className="text-2xl font-bold text-[#0ecb81]">{profile?.officeId ?? 24}</p>
|
||||
</div>
|
||||
<div className="o24-card">
|
||||
<p className="text-xs text-[#848e9c] uppercase">Default currency</p>
|
||||
<p className="text-2xl font-bold text-[#eaecef]">{profile?.settlement?.defaultCurrency ?? 'USD'}</p>
|
||||
<p className="text-xs text-[#848e9c] uppercase">M1 circulating (GL 2100)</p>
|
||||
<p className="text-2xl font-bold text-[#eaecef]">
|
||||
{formatUsd(moneySupply?.M1?.circulating)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="o24-card">
|
||||
<p className="text-xs text-[#848e9c] uppercase">Due from HO (GL 1410)</p>
|
||||
<p className="text-2xl font-bold text-[#3861fb]">
|
||||
{formatUsd(moneySupply?.interoffice?.dueFromHeadOffice)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="o24-card">
|
||||
<p className="text-xs text-[#848e9c] uppercase">M2 tradable tokens</p>
|
||||
@@ -49,6 +58,12 @@ export default function Office24Dashboard() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{moneySupply?.fineractLive === false && (
|
||||
<p className="text-sm text-[#f6465d] mb-6">
|
||||
No Fineract journals for Office 24 yet. Post opening M1: Dr 1410 / Cr 2100 via{' '}
|
||||
<code className="text-[#f0b90b]">scripts/deployment/seed-office-24-opening-journal.mjs</code>
|
||||
</p>
|
||||
)}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
|
||||
<section className="o24-card lg:col-span-1">
|
||||
<h2 className="font-semibold text-[#eaecef] mb-4">Settlement rails</h2>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { DBIS_EXCHANGE_URL, SETTLEMENT_MIDDLEWARE_URL, type DexToken } from '../../config/dex';
|
||||
import type { MoneySupply } from '../central-bank/useCentralBank';
|
||||
|
||||
export type M2Token = DexToken & {
|
||||
moneyLayer?: string;
|
||||
@@ -13,6 +14,7 @@ export function useOffice24() {
|
||||
const [office, setOffice] = useState<Record<string, unknown> | null>(null);
|
||||
const [tokens, setTokens] = useState<M2Token[]>([]);
|
||||
const [health, setHealth] = useState<Record<string, unknown> | null>(null);
|
||||
const [moneySupply, setMoneySupply] = useState<MoneySupply | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const settlement = SETTLEMENT_MIDDLEWARE_URL.replace(/\/$/, '');
|
||||
@@ -21,14 +23,18 @@ export function useOffice24() {
|
||||
const refresh = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const [o, t, h] = await Promise.all([
|
||||
const [o, t, h, ms] = await Promise.all([
|
||||
fetch(`${settlement}/api/v1/settlement/office`).then((r) => r.json()),
|
||||
fetch(`${settlement}/api/v1/settlement/tokens/m2`).then((r) => r.json()),
|
||||
fetch(`${exchange}/api/v1/exchange/health`).then((r) => r.json()),
|
||||
fetch(`${settlement}/api/v1/settlement/money-supply/public`).then((r) =>
|
||||
r.ok ? r.json() : null,
|
||||
),
|
||||
]);
|
||||
setOffice(o);
|
||||
setTokens(Array.isArray(t.tokens) ? t.tokens : []);
|
||||
setHealth(h);
|
||||
setMoneySupply(ms);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -38,5 +44,5 @@ export function useOffice24() {
|
||||
refresh();
|
||||
}, [refresh]);
|
||||
|
||||
return { office, tokens, health, loading, refresh };
|
||||
return { office, tokens, health, moneySupply, loading, refresh };
|
||||
}
|
||||
|
||||
11
frontend-dapp/src/utils/formatMoney.ts
Normal file
11
frontend-dapp/src/utils/formatMoney.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
/** Format Fineract USD balance strings for dashboard display */
|
||||
export function formatUsd(value: string | number | undefined | null): string {
|
||||
if (value === undefined || value === null || value === '') return '—';
|
||||
const n = typeof value === 'number' ? value : parseFloat(String(value));
|
||||
if (!Number.isFinite(n)) return '—';
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
maximumFractionDigits: 2,
|
||||
}).format(n);
|
||||
}
|
||||
Reference in New Issue
Block a user