merge(main): reconcile remote main into x402 verification branch
Some checks failed
CI/CD Pipeline / Solidity Contracts (pull_request) Failing after 54s
CI/CD Pipeline / Security Scanning (pull_request) Successful in 2m39s
CI/CD Pipeline / Lint and Format (pull_request) Failing after 50s
CI/CD Pipeline / Terraform Validation (pull_request) Failing after 25s
CI/CD Pipeline / Kubernetes Validation (pull_request) Successful in 25s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (pull_request) Failing after 24s
OMNL reconcile anchor / Run omnl:reconcile and upload artifacts (pull_request) Failing after 24s
Validation / validate-genesis (pull_request) Successful in 33s
Validation / validate-terraform (pull_request) Failing after 33s
Validation / validate-kubernetes (pull_request) Failing after 12s
Validation / validate-smart-contracts (pull_request) Failing after 14s
Validation / validate-security (pull_request) Failing after 1m18s
Validation / validate-documentation (pull_request) Failing after 22s

This commit is contained in:
defiQUG
2026-07-12 15:14:59 -07:00
225 changed files with 12259 additions and 1606 deletions

View File

@@ -50,6 +50,9 @@ CHAIN_138_RPC_URL=http://192.168.11.221:8545
# === Mirror chain 651940 ===
CHAIN_651940_RPC_URL=https://mainnet-rpc.alltra.global
# === Z Chain — deploy via scripts/deployment/deploy-z-ecosystem-production.sh ===
# Portal: https://zblockchainsystem.com — see config/z-ecosystem.local.env.example
# === Major L1/L2 RPC (set per chain — CHAIN_<id>_RPC_URL) ===
CHAIN_1_RPC_URL=https://eth.llamarpc.com
CHAIN_56_RPC_URL=https://bsc-dataseed.binance.org

View File

@@ -0,0 +1,128 @@
{
"$schema": "OMNL ecosystem link map — canonical URLs for portals, APIs, and local dev",
"version": "1.2.0",
"updated": "2026-07-03",
"notes": [
"Z Ecosystem lives on zblockchainsystem.com — see config/z-ecosystem-links.v1.json",
"Public nginx exposes /settlement/* and /exchange/* without /api/v1/... prefix",
"Tunnel save file logs/public-url-{operator}.txt stores primary portal URL (Z Online Bank)",
"/omnl/* operator UIs ship in frontend dist (public/omnl) — portal-serve proxies /api to explorer",
"POST /z-bank/settle-m0-m4 exists in settlement-middleware source; production may use POST /settlement/external-transfer until deployed",
"SPA anchors #z-full-settlement and #z-settlement-protocols exist on /central-bank"
],
"tunnel": {
"ephemeral": true,
"saveFile": "logs/public-url-ali.txt",
"primaryPath": "/central-bank",
"apps": {
"bridge": "/bridge",
"hub": "/hub",
"zOnlineBank": "/central-bank",
"zFullSettlement": "/central-bank#z-full-settlement",
"wireProtocols": "/central-bank#z-settlement-protocols",
"office24": "/office-24",
"omnlCompliance": "/omnl/compliance",
"omnlDashboard": "/omnl/dashboard",
"omnlTerminal": "/omnl/terminal",
"trade": "/trade",
"swap": "/swap",
"reserve": "/reserve",
"history": "/history",
"wallets": "/wallets",
"admin": "/admin",
"docs": "/docs"
},
"nginxOn3002": {
"settlementHealth": "/settlement/health",
"exchangeHealth": "/exchange/health",
"apiCatalog": "/api/v1/omnl/catalog"
},
"brokenOnServeOnly": []
},
"subjects": [
{ "id": "bridge", "label": "Bridge", "path": "/bridge", "group": "app" },
{ "id": "hub", "label": "DBIS Hub", "path": "/hub", "group": "app" },
{ "id": "zOnlineBank", "label": "Z Online Bank", "path": "/central-bank", "group": "app" },
{ "id": "zFullSettlement", "label": "Z M0-M4 settlement", "path": "/central-bank#z-full-settlement", "group": "app" },
{ "id": "wireProtocols", "label": "Wire protocols", "path": "/central-bank#z-settlement-protocols", "group": "app" },
{ "id": "office24", "label": "Office 24 Central Bank", "path": "/office-24", "group": "app" },
{ "id": "trade", "label": "DBIS Trade", "path": "/trade", "group": "app" },
{ "id": "swap", "label": "Digital Swap", "path": "/swap", "group": "app" },
{ "id": "reserve", "label": "Reserve", "path": "/reserve", "group": "app" },
{ "id": "history", "label": "History", "path": "/history", "group": "app" },
{ "id": "wallets", "label": "Wallets", "path": "/wallets", "group": "app" },
{ "id": "admin", "label": "Admin", "path": "/admin", "group": "app" },
{ "id": "docs", "label": "Developers", "path": "/docs", "group": "app" },
{ "id": "omnlCompliance", "label": "OMNL Compliance", "path": "/omnl/compliance", "group": "console" },
{ "id": "omnlDashboard", "label": "OMNL API snapshot", "path": "/omnl/dashboard", "group": "console" },
{ "id": "omnlTerminal", "label": "Settlement terminal", "path": "/omnl/terminal", "group": "console" },
{ "id": "apiCatalog", "label": "API catalog", "path": "/api/v1/omnl/catalog", "group": "api" },
{ "id": "gazetteNotices", "label": "GUOSMM gazette registry", "path": "/api/v1/omnl/gazette/notices", "group": "api" },
{ "id": "integrationStatus", "label": "Integration status", "path": "/api/v1/omnl/integration-status", "group": "api" },
{ "id": "settlementHealth", "label": "Settlement health", "path": "/settlement/health", "group": "api" },
{ "id": "exchangeHealth", "label": "Exchange health", "path": "/exchange/health", "group": "api" }
],
"local": {
"frontend": {
"port": 3002,
"bridge": "http://localhost:3002/bridge",
"centralBank": "http://localhost:3002/central-bank",
"hub": "http://localhost:3002/hub",
"office24": "http://localhost:3002/office-24",
"trade": "http://localhost:3002/trade",
"swap": "http://localhost:3002/swap"
},
"backend": {
"omnlCatalog": "http://localhost:3000/api/v1/omnl/catalog",
"compliance": "http://localhost:3000/omnl/compliance",
"omnlDashboard": "http://localhost:3000/omnl/dashboard",
"settlementTerminal": "http://localhost:3000/omnl/terminal",
"settlementHealth": "http://localhost:3011/api/v1/settlement/health",
"settlementChains": "http://localhost:3011/api/v1/settlement/chains",
"moneySupplyPublic": "http://localhost:3011/api/v1/settlement/money-supply/public",
"exchangeHealth": "http://localhost:3012/api/v1/exchange/health"
},
"nginx3002": {
"settlementHealth": "http://localhost:3002/settlement/health",
"exchangeHealth": "http://localhost:3002/exchange/health",
"apiCatalog": "http://localhost:3002/api/v1/omnl/catalog"
}
},
"productionPortals": {
"zOnlineBank": "https://online.omdnl.org/central-bank",
"centralBankSecure": "https://secure.omdnl.org/central-bank",
"office24": "https://office24.omdnl.org/office-24",
"exchangeTrade": "https://exchange.omdnl.org/trade",
"forexTrade": "https://forex.omdnl.org/trade",
"omnlBankHybx": "https://bank.omnl.hybxfinance.io",
"exchangeDbisMirror": "https://exchange.d-bis.org/trade",
"centralBankDbisMirror": "https://secure.d-bis.org/central-bank",
"forexDbisMirror": "https://forex.d-bis.org/trade"
},
"productionApis": {
"explorerRoot": "https://explorer.d-bis.org/api/v1",
"catalog": "https://explorer.d-bis.org/api/v1/omnl/catalog",
"gazetteNotices": "https://explorer.d-bis.org/api/v1/omnl/gazette/notices",
"complianceUi": "https://explorer.d-bis.org/omnl/compliance",
"omnlDashboardUi": "https://explorer.d-bis.org/omnl/dashboard",
"settlementTerminalUi": "https://explorer.d-bis.org/omnl/terminal",
"settlementHealth": "https://secure.omdnl.org/settlement/health",
"settlementChains": "https://secure.omdnl.org/settlement/chains",
"moneySupplyPublic": "https://secure.omdnl.org/settlement/public/money-supply",
"externalTransfer": "https://secure.omdnl.org/settlement/external-transfer",
"exchangeHealth": "https://exchange.omdnl.org/exchange/health",
"exchangeTokens": "https://exchange.omdnl.org/exchange/tokens",
"chain138Rpc": "https://rpc-http-pub.d-bis.org",
"chain138Explorer": "https://explorer.d-bis.org",
"fineractSwagger": "https://omnl.hybxfinance.io/fineract-provider/swagger-ui/index.html",
"swiftListenerPublic": "https://swift-listener.d-bis.org"
},
"infra": {
"gitea": "https://gitea.d-bis.org",
"smomRepo": "https://gitea.d-bis.org/d-bis/smom-dbis-138.git",
"guosmmGazette": "https://gazette.shosjj.org/notices/2026",
"swiftListenerLan": "http://192.168.11.114:8788",
"fineractSidecarLan": "http://192.168.11.89:8080",
"chain138RpcLan": "http://192.168.11.211:8545"
}
}

View File

@@ -0,0 +1,37 @@
{
"config": {
"chainId": 900002,
"homesteadBlock": 0,
"eip150Block": 0,
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0,
"berlinBlock": 0,
"londonBlock": 0,
"arrowGlacierBlock": 0,
"grayGlacierBlock": 0,
"mergeNetSplitBlock": 0,
"shanghaiTime": 0,
"cancunTime": 0,
"ibft2": {
"blockperiodseconds": 2,
"epochlength": 30000,
"requesttimeoutseconds": 10
}
},
"nonce": "0x0",
"timestamp": "0x0",
"gasLimit": "0x1c9c380",
"difficulty": "0x1",
"mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
"coinbase": "0x0000000000000000000000000000000000000000",
"alloc": {
"0xfe3b557e8fb62b89f4916b721be55ceb828dbd73": {
"balance": "0x33b2e3c9fd0803ce8000000",
"comment": "Besu dev key #1 — funded for local deploy"
}
}
}

View File

@@ -0,0 +1,115 @@
{
"$schema": "GUOSMM official gazette notices — OMNL / ISB member banks",
"version": "1.0.0",
"gazetteBase": "https://gazette.shosjj.org/notices/2026",
"updated": "2026-07-03",
"notices": [
{
"documentId": "GUOSMM-B-2026-001",
"series": "B",
"title": "Central Bank Charter — OMNL",
"documentType": "Central Bank Charter",
"url": "https://gazette.shosjj.org/notices/2026/GUOSMM-B-2026-001",
"publishedAt": "2026-06-12T12:00:00Z",
"effectiveAt": "2026-06-13T00:00:00Z",
"signedAt": "2026-07-02T00:45:01.965Z",
"signingAuthority": "Sovereign Military Order of Malta",
"contentSha256": "685ebdbfd6a9c1b2c0b7963b7771bf96e96d002e822107207df66f7f593b8032",
"republication": "v2",
"omnlLei": "98450070C57395F6B906",
"scope": ["omnl", "emcb", "central-bank", "office-24", "settlement"],
"enablingActs": ["GUOSMM-A-2026-001"],
"notes": "EMCB model — Treasury issues SCUDO; OMNL administers; not issuer"
},
{
"documentId": "GUOSMM-C-2026-003",
"series": "C",
"title": "zBank — ISB Membership Admission and Operations Charter",
"documentType": "ISB Membership Admission and Operations Charter",
"url": "https://gazette.shosjj.org/notices/2026/GUOSMM-C-2026-003",
"publishedAt": "2026-07-01T20:00:00Z",
"effectiveAt": "2026-05-02T00:00:00Z",
"operationsCommencement": "2026-05-02T00:00:00Z",
"signedAt": "2026-07-03T03:24:07.594Z",
"signingAuthority": "Sovereign Military Order of Malta",
"contentSha256": "c685fd513cbc1e5d20d8f91605f33a8e824680a58b85c67a1a8ef12cf42b71eb",
"licenseId": "LICENSE-ZBANK-SHOSJJ-2026-001",
"licenseClass": "entity.bank",
"entity": "zBank",
"fineractOfficeId": 29,
"fineractExternalId": "ZBANK-OMNL",
"issuingDelegate": "pack-dbis",
"prudentialSupervisor": "pack-omnl",
"parentActs": [
"GUOSMM-A-2026-001",
"GUOSMM-A-2026-002",
"GUOSMM-A-2026-003",
"GUOSMM-B-2026-001",
"GUOSMM-B-2026-002",
"GUOSMM-C-2026-001",
"GUOSMM-C-2026-002"
],
"scope": ["z-bank", "onex-bank", "central-bank", "office-29"]
},
{
"documentId": "GUOSMM-C-2026-004",
"series": "C",
"title": "NovaBank — ISB Membership Admission and Operations Charter",
"documentType": "ISB Membership Admission and Operations Charter",
"url": "https://gazette.shosjj.org/notices/2026/GUOSMM-C-2026-004",
"publishedAt": "2026-07-01T20:00:00Z",
"effectiveAt": "2026-05-02T00:00:00Z",
"operationsCommencement": "2026-05-02T00:00:00Z",
"signedAt": "2026-07-03T03:24:08.276Z",
"signingAuthority": "Sovereign Military Order of Malta",
"contentSha256": "6c524ad5207ae9ca8d53cd4f847593f175740291ad2f519c554bbb823b304b50",
"licenseId": "LICENSE-NOVABANK-SHOSJJ-2026-001",
"licenseClass": "entity.bank",
"entity": "NovaBank",
"fineractOfficeId": 30,
"fineractExternalId": "NOVABANK-OMNL",
"issuingDelegate": "pack-dbis",
"prudentialSupervisor": "pack-omnl",
"parentActs": [
"GUOSMM-A-2026-001",
"GUOSMM-A-2026-002",
"GUOSMM-A-2026-003",
"GUOSMM-B-2026-001",
"GUOSMM-B-2026-002",
"GUOSMM-C-2026-001",
"GUOSMM-C-2026-002"
],
"scope": ["nova-bank", "central-bank", "office-30"]
},
{
"documentId": "GUOSMM-C-2026-005",
"series": "C",
"title": "AnakaBank — ISB Membership Admission and Operations Charter",
"documentType": "ISB Membership Admission and Operations Charter",
"url": "https://gazette.shosjj.org/notices/2026/GUOSMM-C-2026-005",
"publishedAt": "2026-07-01T20:00:00Z",
"effectiveAt": "2026-05-02T00:00:00Z",
"operationsCommencement": "2026-05-02T00:00:00Z",
"signedAt": "2026-07-03T03:24:08.926Z",
"signingAuthority": "Sovereign Military Order of Malta",
"contentSha256": "b93879aeb6e1c4791084b1e327682ccbf9b482a2b9b4fbbaff193e6e7a8f5f3e",
"licenseId": "LICENSE-ANAKABANK-SHOSJJ-2026-001",
"licenseClass": "entity.bank",
"entity": "AnakaBank",
"fineractOfficeId": 31,
"fineractExternalId": "ANAKABANK-OMNL",
"issuingDelegate": "pack-dbis",
"prudentialSupervisor": "pack-omnl",
"parentActs": [
"GUOSMM-A-2026-001",
"GUOSMM-A-2026-002",
"GUOSMM-A-2026-003",
"GUOSMM-B-2026-001",
"GUOSMM-B-2026-002",
"GUOSMM-C-2026-001",
"GUOSMM-C-2026-002"
],
"scope": ["anaka-bank", "central-bank", "office-31"]
}
]
}

View File

@@ -1,24 +1,24 @@
{
"version": "1.0.0",
"updated": "2026-06-20",
"description": "OMNL office settlement lines \u2014 treasury EVM wallets + canonical M1 token anchors per chain.",
"description": "OMNL office settlement lines treasury EVM wallets + canonical M1 token anchors per chain.",
"lines": [
{
"lineId": "0xa16da49f1c038e9d798245a68f33c539b595df5ed39f4c566345c381ac5fd8ad",
"officeId": 1,
"externalId": "1",
"entityName": "OMNL Head Office (DBIS) \u2013 Central Bank",
"entityName": "OMNL Head Office (DBIS) Central Bank",
"walletId": "HYBX-OFFICE-001",
"treasuryWalletMainnet": "0x9baa0efd06a496f401b2fb8508192fec675b6986",
"treasuryWalletChain138": "0xe41a19d5e62d83c086efe71ccecae397d32d2336",
"chains": {
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
},
"1": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
},
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
@@ -33,13 +33,13 @@
"treasuryWalletMainnet": "0x8a72f33cb9f1110dfa2b0a950b887b814614e7ce",
"treasuryWalletChain138": "0xcda90617d6b86698ef54ff1e6bdccbe5df8a54f1",
"chains": {
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
},
"1": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
},
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
@@ -54,13 +54,13 @@
"treasuryWalletMainnet": "0x31d23172792db7d38c19a0d9136b47e5b9cb61bb",
"treasuryWalletChain138": "0x6fba59d90a891588d14bc2ecd63371ebc1974759",
"chains": {
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
},
"1": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
},
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
@@ -75,13 +75,13 @@
"treasuryWalletMainnet": "0x476eac28c5a369556590026a33bf59f7798e2121",
"treasuryWalletChain138": "0x42c4091ab2080a029b3371b473cc22d27ed7170a",
"chains": {
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
},
"1": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
},
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
@@ -96,13 +96,13 @@
"treasuryWalletMainnet": "0x3908d64ec91ad908ed0740654905b85f40c96892",
"treasuryWalletChain138": "0xc536e3a0bb2ada09077f6fef49710ca2c5c7f404",
"chains": {
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
},
"1": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
},
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
@@ -117,13 +117,13 @@
"treasuryWalletMainnet": "0x75aa3ef512ff29a0475954c0a478b6b8a0e7485b",
"treasuryWalletChain138": "0xd1f1d00a3706fc5d680ac8a5bd49dcce17408ea2",
"chains": {
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
},
"1": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
},
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
@@ -138,13 +138,13 @@
"treasuryWalletMainnet": "0x2f0d8f2b449eddbb0e1d57df5b8bab7bc1853fe3",
"treasuryWalletChain138": "0xd437cbf6bf13f2c3b43abd0e5468e66edfe01299",
"chains": {
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
},
"1": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
},
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
@@ -159,13 +159,13 @@
"treasuryWalletMainnet": "0xf75948728a2379b485dc4695233c102f0a261089",
"treasuryWalletChain138": "0x7287fe01c4ab5f70e3d24dd6bc24ae925552bd40",
"chains": {
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
},
"1": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
},
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
@@ -180,13 +180,13 @@
"treasuryWalletMainnet": "0xeaffdc8cff3096712beb4cdc0b0e076d8f3f68c6",
"treasuryWalletChain138": "0xa7ebf614c6e47db20266be996db135d048fa20f7",
"chains": {
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
},
"1": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
},
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
@@ -201,13 +201,13 @@
"treasuryWalletMainnet": "0x73f9180c4d219e3cca43b1c02895c685c4153414",
"treasuryWalletChain138": "0xe5736303f0b60f9dc14ef45b71547c228f261e6e",
"chains": {
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
},
"1": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
},
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
@@ -222,13 +222,13 @@
"treasuryWalletMainnet": "0x16cecc9f7d048c2a8829eea46b3c30fb134523fc",
"treasuryWalletChain138": "0xac894ac9727fae7ff1c82103d74c5eafe5429d1a",
"chains": {
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
},
"1": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
},
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
@@ -243,13 +243,13 @@
"treasuryWalletMainnet": "0x7090a3acdda8514d6b07aff27f91ee8430d0a61c",
"treasuryWalletChain138": "0xdd0a1f686b87349bdc5aa909078688bc065a19f2",
"chains": {
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
},
"1": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
},
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
@@ -264,13 +264,13 @@
"treasuryWalletMainnet": "0x5921918623c5fa3b3798467972497c6809d95790",
"treasuryWalletChain138": "0xcf0acb4afb1e07dc7fb6a6cbbf40d302641f9eaa",
"chains": {
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
},
"1": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
},
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
@@ -285,13 +285,13 @@
"treasuryWalletMainnet": "0xa312cc28d812832d03c72cb822b2b460ade174d1",
"treasuryWalletChain138": "0x8112c72ecfc0493b2cc7622218bc015df97d8ceb",
"chains": {
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
},
"1": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
},
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
@@ -306,13 +306,13 @@
"treasuryWalletMainnet": "0x09c8bbb678649ef543b0ca6e2d0248ed374be70e",
"treasuryWalletChain138": "0x291069ab31457997037ac59296f4fff2b55a033c",
"chains": {
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
},
"1": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
},
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
@@ -327,13 +327,13 @@
"treasuryWalletMainnet": "0xaca2ab7b574d19cee64b004baa46994abf9bf0f2",
"treasuryWalletChain138": "0x0ad3a2a39dbf0c7b1646c2c57902f5756b95e93f",
"chains": {
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
},
"1": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
},
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
@@ -348,13 +348,13 @@
"treasuryWalletMainnet": "0x45a70949a11fc813c82d44f80aec9fc07ff819e4",
"treasuryWalletChain138": "0x3a3ae3e4355ac2a7cbeb1b12d2900eb37763f0b9",
"chains": {
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
},
"1": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
},
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
@@ -369,13 +369,13 @@
"treasuryWalletMainnet": "0x6c2e2c8b8b5ecb82246ceabbcbc862dc2b77634a",
"treasuryWalletChain138": "0x3d3475d9e9bc261372b99dd4f2b0df5dc40623dd",
"chains": {
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
},
"1": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
},
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
@@ -390,13 +390,13 @@
"treasuryWalletMainnet": "0xea6eab4953f7f54f7c1001375e57d573bec66161",
"treasuryWalletChain138": "0x106e4a810aff37a1bc62d30d414ec575e83e7539",
"chains": {
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
},
"1": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
},
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
@@ -411,13 +411,13 @@
"treasuryWalletMainnet": "0x834a95c1f4216e90677334339074fda872b97847",
"treasuryWalletChain138": "0xfd371f2457a6a58f11c80cc1c4ca2305f4674db3",
"chains": {
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
},
"1": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
},
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
@@ -432,13 +432,13 @@
"treasuryWalletMainnet": "0x2e4d9d09801dc09e90b3752db42c1a8e6d033520",
"treasuryWalletChain138": "0xa08621a479bc263e563b2eadb7490d1c07039ba7",
"chains": {
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
},
"1": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
},
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
@@ -453,13 +453,13 @@
"treasuryWalletMainnet": "0x7af34ec0e3d65f147ba6134471bff7bb25f785d0",
"treasuryWalletChain138": "0xa31bf188d4235aa509f6ead064587ee99e6029c6",
"chains": {
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
},
"1": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
},
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
@@ -474,13 +474,13 @@
"treasuryWalletMainnet": "0x8c219794fdb74cf8ded871fab6135e40c4168da9",
"treasuryWalletChain138": "0x204fa6d8c72a21116de75b37abf0d1cd642ddcfa",
"chains": {
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
},
"1": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
},
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
@@ -495,13 +495,13 @@
"treasuryWalletMainnet": "0x87dab11386841366682d6c5fd3cd90587552f8cd",
"treasuryWalletChain138": "0xc93522ae4ab7482572cc29edd68fbb6d5ec6a585",
"chains": {
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
},
"1": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
},
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
@@ -516,13 +516,80 @@
"treasuryWalletMainnet": "0xfc4D24Bda60240D6dfa1a702eE83c65002cf5218",
"treasuryWalletChain138": "0x934adfFFbe4D079b7dbDcE183805dBcBeC7818cb",
"chains": {
"1": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
},
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
"inboundSettlementType": "gnosis-safe"
},
{
"lineId": "0x9f3e2a1b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e1f",
"officeId": 29,
"externalId": "ZBANK-OMNL",
"entityName": "zBank (OneX Bank)",
"walletId": "HYBX-OFFICE-029",
"treasuryWalletMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
"treasuryWalletChain138": "0xc93522ae4ab7482572cc29edd68fbb6d5ec6a585",
"treasuryWalletShivaOneX": "0x0000000000000000000000000000000000000000",
"chains": {
"1": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
},
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
},
"1": {
"900001": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
"tokenM1": "0x0000000000000000000000000000000000000000"
},
"900002": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0x5FbDB2315678afecb367f032d93F642f64180aa3"
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
"inboundSettlementType": "gnosis-safe",
"integrationRef": "config/onex-shiva-integration.v1.json"
},
{
"lineId": "0xa1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef12345678",
"officeId": 30,
"externalId": "NOVABANK-OMNL",
"entityName": "NovaBank",
"walletId": "HYBX-OFFICE-030",
"guosmmNotice": "GUOSMM-C-2026-004",
"treasuryWalletMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
"treasuryWalletChain138": "0xc93522ae4ab7482572cc29edd68fbb6d5ec6a585",
"chains": {
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
"inboundSettlementType": "gnosis-safe"
},
{
"lineId": "0xb2c3d4e5f6789012345678901234567890abcdef1234567890abcdef1234567890",
"officeId": 31,
"externalId": "ANAKABANK-OMNL",
"entityName": "AnakaBank",
"walletId": "HYBX-OFFICE-031",
"guosmmNotice": "GUOSMM-C-2026-005",
"treasuryWalletMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
"treasuryWalletChain138": "0xc93522ae4ab7482572cc29edd68fbb6d5ec6a585",
"chains": {
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",

View File

@@ -3,7 +3,7 @@
"version": "1.0.0",
"officeId": 24,
"externalId": "HOSPITALLERS-ZARDASHT-IRAQ-IRAN",
"name": "HOSPITALLERS Zardasht (Iraq / Iran Operations)",
"name": "Office 24 Central Bank — HOSPITALLERS Zardasht (Iraq / Iran)",
"fineractTenant": "omnl",
"tenantUser": "ali_hospitallers_tenant",
"adminUser": "ali_hospitallers_admin",
@@ -27,5 +27,7 @@
"chain138": { "enabled": true, "rpcEnv": "RPC_URL_138", "defaultLineId": "USD-M2" }
},
"omnlLegalName": "ORGANISATION MONDIALE DU NUMERIQUE L.P.B.C.",
"omnlLei": "98450070C57395F6B906"
"omnlLei": "98450070C57395F6B906",
"guosmmEnablingAct": "GUOSMM-B-2026-001",
"guosmmNoticeUrl": "https://gazette.shosjj.org/notices/2026/GUOSMM-B-2026-001"
}

View File

@@ -0,0 +1,22 @@
{
"$schema": "OMNL Office 29 — zBank (OneX Bank) per GUOSMM-C-2026-003",
"version": "1.0.0",
"officeId": 29,
"externalId": "ZBANK-OMNL",
"name": "zBank — ISB member (OneX Bank)",
"guosmmNotice": "GUOSMM-C-2026-003",
"licenseId": "LICENSE-ZBANK-SHOSJJ-2026-001",
"fineractTenant": "omnl",
"settlement": {
"defaultCurrency": "USD",
"moneyLayers": ["M0", "M1", "M2", "M3", "M4"],
"glM1": "2100",
"glM2": "2200",
"glM4NearMoney": "1000"
},
"rails": {
"swift": { "enabled": true },
"hybx": { "enabled": true },
"chain138": { "enabled": true, "defaultLineId": "USD-M2" }
}
}

View File

@@ -0,0 +1,22 @@
{
"$schema": "OMNL Office 30 — NovaBank per GUOSMM-C-2026-004",
"version": "1.0.0",
"officeId": 30,
"externalId": "NOVABANK-OMNL",
"name": "NovaBank — ISB member",
"guosmmNotice": "GUOSMM-C-2026-004",
"licenseId": "LICENSE-NOVABANK-SHOSJJ-2026-001",
"fineractTenant": "omnl",
"settlement": {
"defaultCurrency": "USD",
"moneyLayers": ["M0", "M1", "M2", "M3", "M4"],
"glM1": "2100",
"glM2": "2200",
"glM4NearMoney": "1000"
},
"rails": {
"swift": { "enabled": true },
"hybx": { "enabled": true },
"chain138": { "enabled": true, "defaultLineId": "USD-M2" }
}
}

View File

@@ -0,0 +1,22 @@
{
"$schema": "OMNL Office 31 — AnakaBank per GUOSMM-C-2026-005",
"version": "1.0.0",
"officeId": 31,
"externalId": "ANAKABANK-OMNL",
"name": "AnakaBank — ISB member",
"guosmmNotice": "GUOSMM-C-2026-005",
"licenseId": "LICENSE-ANAKABANK-SHOSJJ-2026-001",
"fineractTenant": "omnl",
"settlement": {
"defaultCurrency": "USD",
"moneyLayers": ["M0", "M1", "M2", "M3", "M4"],
"glM1": "2100",
"glM2": "2200",
"glM4NearMoney": "1000"
},
"rails": {
"swift": { "enabled": true },
"hybx": { "enabled": true },
"chain138": { "enabled": true, "defaultLineId": "USD-M2" }
}
}

View File

@@ -16,6 +16,8 @@
"https://online.omdnl.org",
"https://office24.omdnl.org",
"https://digital.omdnl.org",
"https://zblockchainsystem.com",
"https://www.zblockchainsystem.com",
"https://exchange.d-bis.org",
"https://exchange.omdnl.org",
"https://forex.omdnl.org"

View File

@@ -41,8 +41,9 @@
},
{
"host": "digital.omdnl.org",
"product": "digital-swap",
"defaultRoute": "/swap"
"product": "digital-swap-legacy",
"defaultRoute": "/swap",
"notes": "Legacy — prefer https://zblockchainsystem.com/swap"
},
{
"host": "office24.omdnl.org",

View File

@@ -1224,37 +1224,23 @@
},
{
"chainId": 900001,
"name": "OMNL Expansion Slot 1",
"name": "Shiva Blockchain (OneX)",
"type": "evm",
"nativeSymbol": "ETH",
"tier": "reserved",
"omnlRole": "expansion",
"status": "reserved",
"nativeSymbol": "SHIVA",
"tier": "primary",
"omnlRole": "onex-shiva",
"status": "active",
"rpcEnv": "CHAIN_900001_RPC_URL",
"explorerUrl": "https://explorer.shiva.onex.omdnl.org",
"rpcDefault": "https://rpc.shiva.onex.omdnl.org",
"settlement": {
"m2LoadEnabled": false,
"swapEnabled": false,
"bridgeEnabled": false,
"officeId": 24
"m2LoadEnabled": true,
"swapEnabled": true,
"bridgeEnabled": true,
"officeId": 29
},
"complianceEnv": "OMNL_COMPLIANCE_CORE_900001"
},
{
"chainId": 900002,
"name": "OMNL Expansion Slot 2",
"type": "evm",
"nativeSymbol": "ETH",
"tier": "reserved",
"omnlRole": "expansion",
"status": "reserved",
"rpcEnv": "CHAIN_900002_RPC_URL",
"settlement": {
"m2LoadEnabled": false,
"swapEnabled": false,
"bridgeEnabled": false,
"officeId": 24
},
"complianceEnv": "OMNL_COMPLIANCE_CORE_900002"
"complianceEnv": "OMNL_COMPLIANCE_CORE_900001",
"integrationRef": "config/onex-shiva-integration.v1.json"
},
{
"chainId": 900003,
@@ -2312,8 +2298,8 @@
}
],
"stats": {
"total": 128,
"active": 61,
"total": 127,
"active": 60,
"staged": 3,
"reserved": 64
}

View File

@@ -0,0 +1,46 @@
{
"$schema": "OneX Wallet + OneX Bank on Shiva Blockchain — OMNL integration",
"version": "1.0.0",
"updated": "2026-07-02",
"brand": {
"wallet": "OneX Wallet",
"bank": "OneX Bank",
"blockchain": "Shiva Blockchain"
},
"chain": {
"chainId": 900001,
"name": "Shiva Blockchain (OneX)",
"network": "shiva-onex",
"type": "evm",
"nativeSymbol": "SHIVA",
"rpcEnv": "CHAIN_900001_RPC_URL",
"rpcDefault": "https://rpc.shiva.onex.omdnl.org",
"explorerUrl": "https://explorer.shiva.onex.omdnl.org",
"omnlRegistrySlot": 900001
},
"fineract": {
"officeId": 29,
"externalId": "ZBANK-OMNL",
"entityName": "zBank",
"hybxWalletId": "HYBX-OFFICE-029",
"m1SpendableGl": "2100"
},
"bankCorridor": {
"orderingName": "OneX Bank",
"creditorBic": "ONEXSHIV33",
"debtorIban": "SH98ONEX000000000000000029",
"swiftMessageKind": "MT103",
"remittanceTemplate": "OneX Bank → Z Online Bank · Shiva blockchain settlement"
},
"wallet": {
"thirdwebMetadataName": "OneX Wallet",
"supportedAuth": ["email", "google", "apple", "passkey"],
"defaultTokenLineId": "USD-M2",
"defaultTokenSymbol": "cUSDC"
},
"settlement": {
"zBridgeOfficeId": 24,
"targetChainId": 900001,
"rails": ["HYBX", "SWIFT", "CHAIN_MINT"]
}
}

View File

@@ -47,5 +47,10 @@
"allowHybxProduction": true,
"allowChainMintExecute": true,
"onlineBankMode": true
},
"guosmm": {
"emcbCharter": "GUOSMM-B-2026-001",
"registryPath": "config/guosmm-notices.v1.json",
"gazetteBase": "https://gazette.shosjj.org/notices/2026"
}
}

View File

@@ -0,0 +1,110 @@
{
"$schema": "OMNL SWIFT FIN / ISO 20022 listener — Chain 138 + Office 24 production",
"schemaVersion": "1.0.0",
"chain138": {
"chainId": 138,
"names": ["chain138", "chain 138", "defi oracle meta", "dbis", "omnl"],
"rpcHint": "RPC_URL_138",
"configSources": [
"config/ei-matrix-138-assets-and-pools.v1.json",
"config/chain138-iso20022-attestation.v1.json"
]
},
"supportedSwiftFinTypes": ["MT103", "MT102", "MT202", "MT910", "MT940", "MT942", "MT950"],
"supportedIso20022Types": [
"pain.001",
"pacs.008",
"pacs.009",
"pacs.002",
"camt.052",
"camt.053",
"camt.054"
],
"cToCwSymbolMapping": {
"USD": "cWUSD",
"USDC": "cWUSDC",
"USDT": "cWUSDT",
"EUR": "cWEUR",
"GBP": "cWGBP"
},
"matchHints": {
"instructionIdPrefixes": ["HOSPITALLERS", "CHAIN138", "OMNL", "HYBX", "ZARDASHT", "Z-BANK", "O24"],
"remittanceKeywords": [
"chain138",
"cWUSDC",
"cWUSDT",
"cWUSD",
"omnl",
"hybx",
"office 24",
"zardasht",
"hospitallers"
]
},
"storage": {
"eventsDir": "reports/swift-listener/events",
"latestStatus": "reports/swift-listener/status-latest.json",
"processedIndex": "reports/swift-listener/processed-index.json",
"inboxDir": "reports/swift-listener/inbox",
"archiveDir": "reports/swift-listener/archive",
"as4InboxDir": "reports/swift-listener/as4-inbox",
"mailInboxDir": "reports/swift-listener/mail-inbox",
"telexInboxDir": "reports/swift-listener/telex-inbox"
},
"adapters": {
"fileInbox": { "enabled": true, "pollIntervalSec": 30, "archiveOnProcess": true },
"webhook": {
"enabled": true,
"port": 8788,
"path": "/inbound",
"healthPath": "/health",
"authBearerEnv": "SWIFT_LISTENER_WEBHOOK_TOKEN",
"webhookSecretEnv": "HYBX_WEBHOOK_SECRET",
"webhookSignatureHeader": "x-hybx-signature"
},
"p2pRail": {
"enabled": false,
"baseUrlEnv": "P2P_BASE_URL",
"pollIntervalSec": 60,
"transactionsPath": "/api/v1/transactions"
},
"finGateway": {
"enabled": false,
"baseUrlEnv": "FIN_GATEWAY_URL",
"fallbackBaseUrlEnv": "ALLIANCE_ACCESS_URL",
"pollIntervalSec": 120,
"messagesPath": "/messages",
"authBearerEnv": "FIN_GATEWAY_TOKEN"
},
"swiftFinTcp": {
"enabled": true,
"host": "0.0.0.0",
"portEnv": "SWIFT_FIN_TCP_PORT",
"defaultPort": 5001
},
"as4Inbox": { "enabled": false, "pollIntervalSec": 60, "archiveOnProcess": true },
"mailAttachmentInbox": { "enabled": false, "pollIntervalSec": 120, "archiveOnProcess": true },
"telexInbox": { "enabled": false, "pollIntervalSec": 120, "archiveOnProcess": true }
},
"outbound": {
"omnlForward": {
"enabled": true,
"mode": "local_and_api",
"apiPath": "/api/v1/omnl/iso20022/messages",
"tokenAggregationUrlEnv": "TOKEN_AGGREGATION_URL",
"apiKeyEnv": "OMNL_API_KEY",
"localStoreDir": "config/iso20022-omnl/messages"
},
"intakeGateway": {
"enabled": true,
"gatewayAddressEnv": "ISO20022_INTAKE_GATEWAY_MAINNET",
"gatewayAddressFallback": "0x66C89F91A4e830d87fcE4E5F86aFA71A96FbDa3d",
"rpcUrlEnv": "RPC_URL_138",
"privateKeyEnv": "PRIVATE_KEY",
"executeEnv": "SWIFT_LISTENER_GATEWAY_EXECUTE",
"dryRunDefault": true,
"autoExecuteWhenKeyPresent": true,
"chainId": 138
}
}
}

View File

@@ -0,0 +1,72 @@
{
"$schema": "OMNL SWIFT MT catalog — Central Bank, Office 24, Z Online Bank",
"version": "1.0.0",
"settlementOfficeId": 24,
"bic": "OMNLUS33",
"rails": {
"outbound": "POST /settlement/external-transfer (rail SWIFT)",
"inbound": "POST /settlement/swift/inbound",
"listener": "SWIFT listener webhook POST /inbound :8788"
},
"messageTypes": [
{
"mt": "MT103",
"name": "Single Customer Credit Transfer",
"use": "Z Bank wire, Office 24 outbound, correspondent payments",
"moneyLayers": ["M0", "M1", "M2", "M4"],
"portal": "/central-bank#z-settlement-protocols",
"settlementField": "swiftMessageKind: MT103"
},
{
"mt": "MT102",
"name": "Multiple Customer Credit Transfer",
"use": "Bulk payroll, DLC/LS batch legs",
"moneyLayers": ["M1", "M2", "M3", "M4"],
"portal": "/central-bank#z-settlement-protocols"
},
{
"mt": "MT202",
"name": "General Financial Institution Transfer",
"use": "SBLC cover payment, interbank settlement",
"moneyLayers": ["M4"],
"tradeFinance": ["SBLC"]
},
{
"mt": "MT910",
"name": "Confirmation of Credit",
"use": "Credit advice to beneficiary bank",
"moneyLayers": ["M4"]
},
{
"mt": "MT940",
"name": "Customer Statement",
"use": "End-of-day account statement (Fineract reconcile)",
"moneyLayers": ["M0", "M1", "M2"]
},
{
"mt": "MT942",
"name": "Interim Statement",
"use": "Intraday statement / liquidity monitoring",
"moneyLayers": ["M0", "M1"]
},
{
"mt": "MT950",
"name": "Statement Message",
"use": "Legacy statement (Fineract GL export)",
"moneyLayers": ["M0", "M1", "M2"]
}
],
"iso20022": [
{ "type": "pacs.008", "swiftFin": "MT103", "use": "FI-to-FI customer credit" },
{ "type": "pacs.009", "swiftFin": "MT202", "use": "FI-to-FI institution transfer" },
{ "type": "camt.054", "swiftFin": "MT910", "use": "Credit notification" },
{ "type": "camt.053", "swiftFin": "MT940", "use": "Account statement" },
{ "type": "camt.052", "swiftFin": "MT942", "use": "Account report" }
],
"tradeFinance": {
"SBLC": { "swiftMessages": ["MT103", "MT202"], "endpoint": "POST /settlement/trade-finance/SBLC" },
"DLC": { "swiftMessages": ["MT103", "MT102"], "endpoint": "POST /settlement/trade-finance/DLC" },
"BG": { "swiftMessages": ["MT103"], "endpoint": "POST /settlement/trade-finance/BG" },
"LS": { "swiftMessages": ["MT102", "MT103"], "endpoint": "POST /settlement/trade-finance/LS" }
}
}

View File

@@ -0,0 +1,17 @@
{
"$schema": "Z Chain on-chain deployment addresses (chainId 900002)",
"version": "1.0.0",
"updated": "2026-07-07",
"chainId": 900002,
"network": "z-chain",
"status": "active",
"contracts": {
"cUSDC": "0x5FbDB2315678afecb367f032d93F642f64180aa3",
"accountWalletRegistry": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512"
},
"deployScript": "script/z-chain/DeployZChainEmoney.s.sol",
"notes": [
"Run scripts/deployment/deploy-z-chain-contracts.sh after bootstrap-z-chain-local",
"Update addresses here and in hybx-omnl-cross-chain-lines.json chains.900002"
]
}

View File

@@ -0,0 +1,105 @@
{
"$schema": "International Z Wallet + Z Chain — Z Ecosystem integration",
"version": "1.0.0",
"updated": "2026-07-05",
"brand": {
"wallet": "International Z Wallet",
"bank": "Z Online Bank",
"blockchain": "Z Chain",
"bot": "Z Bot"
},
"chain": {
"chainId": 900002,
"name": "Z Chain",
"network": "z-chain",
"type": "evm",
"nativeSymbol": "Z",
"rpcEnv": "CHAIN_900002_RPC_URL",
"rpcDefault": "http://127.0.0.1:8546",
"explorerUrl": "https://explorer.zblockchainsystem.com",
"omnlRegistrySlot": 900002
},
"fineract": {
"officeId": 29,
"externalId": "ZBANK-OMNL",
"entityName": "zBank",
"hybxWalletId": "HYBX-OFFICE-029",
"m1SpendableGl": "2100"
},
"bankCorridor": {
"orderingName": "International Z Wallet",
"creditorBic": "ZCHNOMNL33",
"debtorIban": "Z298ZCHN000000000000000029",
"swiftMessageKind": "MT103",
"remittanceTemplate": "International Z Wallet → Z Online Bank · Z Chain settlement"
},
"internationalCorridors": [
{
"id": "IQ-USD",
"label": "Iraq (USD)",
"country": "IQ",
"currency": "USD",
"rails": [
"SWIFT",
"HYBX",
"CHAIN_MINT"
]
},
{
"id": "IR-USD",
"label": "Iran (USD)",
"country": "IR",
"currency": "USD",
"rails": [
"HYBX",
"CHAIN_MINT"
]
},
{
"id": "EU-EUR",
"label": "Europe (EUR)",
"country": "EU",
"currency": "EUR",
"rails": [
"SWIFT",
"HYBX"
]
},
{
"id": "GLOBAL-USD",
"label": "Global (USD)",
"country": "GLOBAL",
"currency": "USD",
"rails": [
"SWIFT",
"HYBX",
"CHAIN_MINT"
]
}
],
"wallet": {
"thirdwebMetadataName": "International Z Wallet",
"supportedAuth": [
"email",
"google",
"apple",
"passkey"
],
"defaultTokenLineId": "USD-M2",
"defaultTokenSymbol": "cUSDC"
},
"settlement": {
"zBridgeOfficeId": 24,
"targetChainId": 900002,
"rails": [
"HYBX",
"SWIFT",
"CHAIN_MINT"
]
},
"contracts": {
"cUSDC": "0x5FbDB2315678afecb367f032d93F642f64180aa3",
"accountWalletRegistry": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
"deployRef": "config/z-chain-deployed.v1.json"
}
}

View File

@@ -0,0 +1,29 @@
{
"genesis": {
"config": {
"chainId": 900002,
"ibft2": {
"blockperiodseconds": 2,
"epochlength": 30000,
"requesttimeoutseconds": 10
}
},
"nonce": "0x0",
"timestamp": "0x0",
"gasLimit": "0x1c9c380",
"difficulty": "0x1",
"mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
"coinbase": "0x0000000000000000000000000000000000000000",
"alloc": {
"0xfe3b557e8fb62b89f4916b721be55ceb828dbd73": {
"balance": "0x33b2e3c9fd0803ce8000000"
}
}
},
"blockchain": {
"nodes": {
"generate": true,
"count": 1
}
}
}

View File

@@ -0,0 +1,37 @@
{
"$schema": "Z Chain production endpoints — Besu IBFT2 on LXC or Azure",
"version": "1.0.0",
"updated": "2026-07-06",
"chainId": 900002,
"network": "z-chain",
"genesis": "config/genesis-z-chain-900002.json",
"networkConfig": "config/z-chain-network-config.json",
"endpoints": {
"portal": "https://zblockchainsystem.com",
"rpcPublic": "https://rpc.zblockchainsystem.com",
"rpcLan": "http://192.168.11.212:8546",
"explorer": "https://explorer.zblockchainsystem.com",
"walletPortal": "https://zblockchainsystem.com/z-wallet",
"ecosystemHub": "https://zblockchainsystem.com/z",
"digitalSwap": "https://zblockchainsystem.com/swap",
"zBankUi": "https://zblockchainsystem.com/central-bank"
},
"env": {
"rpc": "CHAIN_900002_RPC_URL",
"compliance": "OMNL_COMPLIANCE_CORE_900002",
"viteRpc": "VITE_RPC_URL_900002",
"viteExplorer": "VITE_Z_CHAIN_EXPLORER_URL"
},
"besu": {
"containerName": "z-chain-besu-prod",
"rpcPort": 8546,
"dataPath": "/opt/besu-z-chain",
"genesisPath": "/opt/besu-z-chain/config/genesis.json"
},
"deployScripts": {
"bootstrapLocal": "scripts/deployment/bootstrap-z-chain-local.sh",
"bootstrapProduction": "scripts/deployment/bootstrap-z-chain-production.sh",
"deployContracts": "scripts/deployment/deploy-z-chain-contracts.sh",
"syncAddresses": "scripts/deployment/sync-z-chain-deployed-addresses.mjs"
}
}

View File

@@ -0,0 +1,35 @@
{
"$schema": "Z Ecosystem link map — zblockchainsystem.com only",
"version": "1.0.0",
"updated": "2026-07-07",
"brand": "Z Blockchain System",
"publicUrl": "https://zblockchainsystem.com",
"routes": {
"hub": "/z",
"wallet": "/z-wallet",
"swap": "/swap",
"onlineBank": "/central-bank",
"zBotHealth": "/api/v1/z-bot/health",
"zWalletCorridors": "/api/v1/z-wallet/corridors",
"settlementHealth": "/settlement/health",
"zBankSettle": "/settlement/z-bank/settle-m0-m4"
},
"productionPortals": {
"zEcosystem": "https://zblockchainsystem.com/z",
"zWallet": "https://zblockchainsystem.com/z-wallet",
"zSwap": "https://zblockchainsystem.com/swap",
"zOnlineBank": "https://zblockchainsystem.com/central-bank"
},
"subdomains": {
"rpc": "https://rpc.zblockchainsystem.com",
"explorer": "https://explorer.zblockchainsystem.com"
},
"local": {
"frontend": "http://localhost:3002",
"zHub": "http://localhost:3002/z",
"zWallet": "http://localhost:3002/z-wallet",
"zBotHealth": "http://localhost:3000/api/v1/z-bot/health",
"settlementHealth": "http://localhost:3011/api/v1/settlement/health",
"chainRpc": "http://127.0.0.1:8546"
}
}

View File

@@ -0,0 +1,28 @@
{
"version": "1.0.0",
"updated": "2026-07-06",
"brand": "Z Blockchain System",
"publicUrl": "https://zblockchainsystem.com",
"originHost": "dev-bis-zardasht",
"originIp": "76.53.10.34",
"frontendPort": 3002,
"domains": [
{
"host": "zblockchainsystem.com",
"hostAliases": ["www.zblockchainsystem.com"],
"product": "z-ecosystem",
"defaultRoute": "/z",
"routes": ["/z", "/z-wallet", "/swap", "/central-bank"]
}
],
"subdomains": {
"rpc": "rpc.zblockchainsystem.com",
"explorer": "explorer.zblockchainsystem.com"
},
"apis": {
"zBotHealth": "https://zblockchainsystem.com/api/v1/z-bot/health",
"zWalletCorridors": "https://zblockchainsystem.com/api/v1/z-wallet/corridors",
"settlementHealth": "https://zblockchainsystem.com/settlement/health",
"zBankSettle": "https://zblockchainsystem.com/settlement/z-bank/settle-m0-m4"
}
}

View File

@@ -0,0 +1,23 @@
# Z Ecosystem local overrides — copy to config/z-ecosystem.local.env
# Production portal: https://zblockchainsystem.com (separate from OMNL)
VITE_ECOSYSTEM=z
Z_SUPPORTED_CHAINS_CONFIG=config/z-supported-chains.v1.json
Z_ECOSYSTEM_PUBLIC_URL=https://zblockchainsystem.com
VITE_Z_ECOSYSTEM_URL=https://zblockchainsystem.com
# Z Chain (local Hardhat/Besu)
CHAIN_900002_RPC_URL=http://127.0.0.1:8546
VITE_RPC_URL_900002=http://127.0.0.1:8546
VITE_Z_CHAIN_EXPLORER_URL=https://explorer.zblockchainsystem.com
VITE_ENABLE_Z_WALLET=true
# Z Bot — Azure OpenAI (optional)
AZURE_OPENAI_ENDPOINT=https://<resource>.openai.azure.com
AZURE_OPENAI_API_KEY=
AZURE_OPENAI_DEPLOYMENT=gpt-4o-mini
AZURE_OPENAI_API_VERSION=2024-02-15-preview
# Settlement (Z Bank API backend)
SETTLEMENT_MIDDLEWARE_URL=http://127.0.0.1:3011
SETTLEMENT_MIDDLEWARE_CONFIG=config/settlement-middleware.production.v1.json

View File

@@ -0,0 +1,25 @@
# Z Ecosystem production — copy to config/z-ecosystem.production.env
# Gitea binds :3000 on dev-bis-ali; Z API uses :3100
VITE_ECOSYSTEM=z
Z_SUPPORTED_CHAINS_CONFIG=config/z-supported-chains.v1.json
Z_ECOSYSTEM_PUBLIC_URL=https://zblockchainsystem.com
VITE_Z_ECOSYSTEM_URL=https://zblockchainsystem.com
CHAIN_900002_RPC_URL=https://rpc.zblockchainsystem.com
VITE_RPC_URL_900002=https://rpc.zblockchainsystem.com
VITE_Z_CHAIN_EXPLORER_URL=https://explorer.zblockchainsystem.com
VITE_ENABLE_Z_WALLET=true
Z_TOKEN_AGGREGATION_PORT=3100
PORT=3100
TOKEN_AGGREGATION_URL=http://127.0.0.1:3100
SETTLEMENT_MIDDLEWARE_URL=http://127.0.0.1:3011
SETTLEMENT_URL=http://127.0.0.1:3011
SETTLEMENT_MIDDLEWARE_CONFIG=config/settlement-middleware.production.v1.json
# Z Bot — Azure OpenAI (optional)
AZURE_OPENAI_ENDPOINT=
AZURE_OPENAI_API_KEY=
AZURE_OPENAI_DEPLOYMENT=gpt-4o-mini
AZURE_OPENAI_API_VERSION=2024-02-15-preview

View File

@@ -0,0 +1,50 @@
{
"$schema": "Z consumer ecosystem hub links",
"version": "1.1.0",
"updated": "2026-07-06",
"brand": "Z Blockchain System",
"publicUrl": "https://zblockchainsystem.com",
"routes": {
"hub": "/z",
"wallet": "/z-wallet",
"bot": "/z-wallet#z-bot",
"swap": "/swap",
"onlineBank": "/central-bank",
"settlement": "/central-bank#z-full-settlement"
},
"apps": [
{
"id": "zWallet",
"label": "International Z Wallet",
"path": "/z-wallet",
"description": "Thirdweb in-app wallet on Z Chain with international corridors"
},
{
"id": "zBot",
"label": "Z Bot",
"path": "/z-wallet#z-bot",
"description": "In-app assistant for balance, transfers, and corridors"
},
{
"id": "zChain",
"label": "Z Chain",
"path": "/z",
"chainId": 900002,
"description": "Consumer EVM chain (chainId 900002)"
},
{
"id": "zSwap",
"label": "Digital Swap",
"path": "/swap",
"description": "M2 load, token swap, settlement rails"
},
{
"id": "zOnlineBank",
"label": "Z Online Bank",
"path": "/central-bank",
"description": "M0M4 banking and settlement rails"
}
],
"integrationRef": "config/z-chain-integration.v1.json",
"productionDomainsRef": "config/z-ecosystem-production-domains.v1.json"
}

View File

@@ -0,0 +1,27 @@
{
"$schema": "Z Chain supported chains — separate from OMNL 128-chain registry",
"version": "1.0.0",
"brand": "Z Blockchain System",
"primaryChainId": 900002,
"chains": [
{
"chainId": 900002,
"name": "Z Chain",
"type": "evm",
"nativeSymbol": "Z",
"tier": "primary",
"zRole": "settlement-hub",
"status": "active",
"rpcEnv": "CHAIN_900002_RPC_URL",
"explorerUrl": "https://explorer.zblockchainsystem.com",
"rpcDefault": "http://127.0.0.1:8546",
"settlement": {
"m2LoadEnabled": true,
"swapEnabled": true,
"bridgeEnabled": true
},
"complianceEnv": "Z_COMPLIANCE_CORE_900002",
"integrationRef": "config/z-chain-integration.v1.json"
}
]
}

View File

@@ -0,0 +1,39 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/access/AccessControl.sol";
interface IAccountWalletRegistryZ {
struct WalletLink {
bytes32 walletRefId;
string provider;
bool active;
}
function linkAccountToWallet(bytes32 accountRefId, bytes32 walletRefId, string calldata provider) external;
function getWalletsForAccount(bytes32 accountRefId) external view returns (WalletLink[] memory);
}
/**
* @title AccountWalletRegistryZ
* @notice Minimal registry for Z Chain deploy scope
*/
contract AccountWalletRegistryZ is IAccountWalletRegistryZ, AccessControl {
bytes32 public constant ACCOUNT_MANAGER_ROLE = keccak256("ACCOUNT_MANAGER_ROLE");
mapping(bytes32 => WalletLink[]) private _accountToWallets;
constructor(address admin) {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(ACCOUNT_MANAGER_ROLE, admin);
}
function linkAccountToWallet(bytes32 accountRefId, bytes32 walletRefId, string calldata provider) external {
require(hasRole(ACCOUNT_MANAGER_ROLE, msg.sender), "not manager");
_accountToWallets[accountRefId].push(WalletLink(walletRefId, provider, true));
}
function getWalletsForAccount(bytes32 accountRefId) external view returns (WalletLink[] memory) {
return _accountToWallets[accountRefId];
}
}

View File

@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
/**
* @title ZChainUSDC
* @notice Minimal M2 stand-in for Z Chain local/dev deploys (cUSDC)
*/
contract ZChainUSDC is ERC20 {
constructor(address admin) ERC20("Z Chain USD", "cUSDC") {
_mint(admin, 1_000_000_000 * 10 ** 6);
}
function decimals() public pure override returns (uint8) {
return 6;
}
}

View File

@@ -1,5 +1,10 @@
# Shared OMNL Bank proxy locations (include inside each server block)
# Z Ecosystem routes live on zblockchainsystem.com only
location ~ ^/(z|z-wallet)(/|$) {
return 302 https://zblockchainsystem.com$request_uri;
}
location /api/v1/ {
include /etc/nginx/omnl-portal-auth-current.conf.inc;
proxy_pass http://omnl_api/api/v1/;

View File

@@ -0,0 +1,56 @@
# Z Ecosystem — Z-only nginx locations (no OMNL consoles)
location /api/v1/ {
proxy_pass http://z_ecosystem_api/api/v1/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 120s;
}
location /settlement/ {
proxy_pass http://z_ecosystem_settlement/api/v1/settlement/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 120s;
}
location /exchange/ {
proxy_pass http://z_ecosystem_exchange/api/v1/exchange/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_read_timeout 120s;
}
# Block OMNL / DBIS-only SPA routes on Z domain
location ~ ^/(hub|office-24|trade|bridge|reserve|history|admin|docs|wallets|omnl)(/|$) {
return 404;
}
location /health {
return 200 'Z Ecosystem online — chain 900002';
add_header Content-Type text/plain;
}
location /assets/ {
proxy_pass http://z_ecosystem_frontend;
proxy_http_version 1.1;
proxy_set_header Host $host;
expires 1y;
add_header Cache-Control "public, immutable";
}
location / {
proxy_pass http://z_ecosystem_frontend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
}

View File

@@ -0,0 +1,49 @@
# Z Ecosystem — zblockchainsystem.com (Z Chain, Wallet, Bot, Swap, Z Bank)
# Deploy: scripts/deployment/deploy-z-domains.sh
# Independent from OMNL / DBIS portals
upstream z_ecosystem_frontend {
server 127.0.0.1:3002;
}
upstream z_ecosystem_api {
server 127.0.0.1:3100;
}
upstream z_ecosystem_settlement {
server 127.0.0.1:3011;
}
upstream z_ecosystem_exchange {
server 127.0.0.1:3012;
}
server {
listen 80;
listen [::]:80;
server_name zblockchainsystem.com www.zblockchainsystem.com;
add_header X-Z-Product "z-ecosystem" always;
add_header X-Z-Chain "900002" always;
location = / {
return 302 /z;
}
include /etc/nginx/snippets/z-ecosystem-common-locations.conf;
}
server {
listen 80;
listen [::]:80;
server_name rpc.zblockchainsystem.com;
add_header X-Z-Product "z-chain-rpc" always;
location / {
proxy_pass http://127.0.0.1:8546;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_read_timeout 120s;
}
}

View File

@@ -0,0 +1,157 @@
# Z Ecosystem — Z Chain, International Z Wallet, Z Bot
Consumer-facing Z products on OMNL expansion slot **900002**.
## Components
| Product | Route | Config |
|---------|-------|--------|
| Z Ecosystem hub | `/z` | `config/z-ecosystem.v1.json` |
| International Z Wallet | `/z-wallet` | `config/z-chain-integration.v1.json` |
| Z Bot (in-app chat) | `/z-wallet#z-bot` | `POST /api/v1/z-bot/chat` |
| Z Online Bank | `/central-bank` | existing z-bank settlement |
## Local Z Chain bootstrap
```bash
# Start Besu (chainId 900002) on port 8546
bash scripts/deployment/bootstrap-z-chain-local.sh
# Env
export CHAIN_900002_RPC_URL=http://127.0.0.1:8546
export VITE_RPC_URL_900002=http://127.0.0.1:8546
# Deploy contracts (optional)
export PRIVATE_KEY=0x...
bash scripts/deployment/deploy-z-chain-contracts.sh
```
Windows:
```powershell
.\scripts\deployment\bootstrap-z-chain-local.ps1
```
Verify: `cast chain-id --rpc-url http://127.0.0.1:8546``900002` (hex `0xdbba2`)
### One-command complete setup (Windows)
```powershell
npm install --legacy-peer-deps
.\scripts\deployment\complete-z-chain.ps1
```
Uses Hardhat node when Docker is unavailable. Keeps node running in background (`logs/z-chain-node.pid`).
## Frontend env
```env
VITE_ENABLE_Z_WALLET=true
VITE_RPC_URL_900002=http://127.0.0.1:8546
VITE_Z_CHAIN_EXPLORER_URL=https://explorer.z.omdnl.org
VITE_THIRDWEB_CLIENT_ID=...
```
## API
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/z-wallet/corridors` | International corridor catalog |
| GET | `/api/v1/z-wallet/status/:ref` | Settlement status proxy |
| GET | `/api/v1/z-bot/health` | Z Bot + Azure OpenAI config status |
| POST | `/api/v1/z-bot/chat` | Z Bot assistant |
Z Bot body:
```json
{
"sessionId": "zbot-...",
"message": "What is my balance?",
"walletAddress": "0x...",
"chainId": 900002
}
```
## Z Bot LLM (optional)
Set Azure OpenAI for live replies; otherwise rule-based fallback:
```env
AZURE_OPENAI_ENDPOINT=https://<resource>.openai.azure.com
AZURE_OPENAI_API_KEY=...
AZURE_OPENAI_DEPLOYMENT=gpt-4o-mini
AZURE_OPENAI_API_VERSION=2024-02-15-preview
```
## International corridors
Defined in `config/z-chain-integration.v1.json`:
- Iraq (USD) — SWIFT, HYBX, CHAIN_MINT
- Iran (USD) — HYBX, CHAIN_MINT
- Europe (EUR) — SWIFT, HYBX
- Global (USD) — SWIFT, HYBX, CHAIN_MINT
Transfers submit to `POST /z-bank/settle-m0-m4` (or `/external-transfer` fallback) with `targetChainId: 900002`.
## Registry
`config/omnl-supported-chains.v1.json` slot **900002**`name: Z Chain`, `status: active`, `omnlRole: z-chain`.
## Production Besu (LXC / VM)
On the Z Chain host (see `config/z-chain-production.v1.json`):
```bash
sudo bash scripts/deployment/bootstrap-z-chain-production.sh
```
Wire nginx/Cloudflare:
- `https://zblockchainsystem.com` → portal (`/z`, `/z-wallet`, `/swap`, `/central-bank`)
- `https://rpc.zblockchainsystem.com``127.0.0.1:8546`
- `https://explorer.zblockchainsystem.com` → Blockscout (optional)
Set on Z portal host:
```env
CHAIN_900002_RPC_URL=https://rpc.zblockchainsystem.com
VITE_RPC_URL_900002=https://rpc.zblockchainsystem.com
VITE_Z_CHAIN_EXPLORER_URL=https://explorer.zblockchainsystem.com
VITE_Z_ECOSYSTEM_URL=https://zblockchainsystem.com
```
Deploy:
```bash
bash scripts/deployment/deploy-z-ecosystem-production.sh
bash scripts/deployment/print-z-dns-records.sh
```
## Local dev without Docker
If Docker Desktop is not running, use Anvil:
```powershell
.\scripts\deployment\bootstrap-z-chain-anvil.ps1
```
Default Anvil account #0 key: `0xac0974bec39a17e36ba4a590bcafb95bd05417ff81f81088a92f31fcfcf35588`
```bash
export PRIVATE_KEY=0xac0974bec39a17e36ba4a590bcafb95bd05417ff81f81088a92f31fcfcf35588
bash scripts/deployment/deploy-z-chain-contracts.sh
node scripts/deployment/sync-z-chain-deployed-addresses.mjs
```
## Contract addresses
After deploy, sync all configs:
```bash
node scripts/deployment/sync-z-chain-deployed-addresses.mjs
```
Updates `z-chain-deployed.v1.json`, `z-chain-integration.v1.json`, and `hybx-omnl-cross-chain-lines.json`.

View File

@@ -143,7 +143,6 @@ forge script script/DeployTransactionMirror.s.sol \
3. **RPC Providers**:
- Alchemy: https://www.alchemy.com/
- Infura: https://www.infura.io/
- QuickNode: https://www.quicknode.com/
4. **Verification**: Contracts will be automatically verified on Etherscan if `ETHERSCAN_API_KEY` is set
---

View File

@@ -46,9 +46,6 @@ Switch to Alchemy or another provider:
# Alchemy
ETHEREUM_MAINNET_RPC=https://eth-mainnet.g.alchemy.com/v2/YOUR_ALCHEMY_API_KEY
# QuickNode
ETHEREUM_MAINNET_RPC=https://your-endpoint.quiknode.pro/YOUR_API_KEY
# Public RPC (not recommended for production)
ETHEREUM_MAINNET_RPC=https://eth.llamarpc.com
```

View File

@@ -62,11 +62,6 @@ Update `.env` line 18 with alternative provider:
ETHEREUM_MAINNET_RPC=https://eth-mainnet.g.alchemy.com/v2/YOUR_ALCHEMY_API_KEY
```
**QuickNode**:
```bash
ETHEREUM_MAINNET_RPC=https://your-endpoint.quiknode.pro/YOUR_API_KEY
```
**Public RPC** (not recommended for production):
```bash
ETHEREUM_MAINNET_RPC=https://eth.llamarpc.com

View File

@@ -68,6 +68,12 @@ via_ir = true
# Generic deploy profile for post-Dencun EVMs. Do not use for Chain 138.
evm_version = "cancun"
[profile.zchain]
optimizer = true
optimizer_runs = 100
via_ir = true
evm_version = "cancun"
[profile.chain138]
optimizer = true
optimizer_runs = 100
@@ -169,6 +175,7 @@ cbor_metadata = false
# Prevents default localhost:8545 when ETH_RPC_URL not set
[rpc_endpoints]
chain138 = "http://192.168.11.211:8545"
zchain = "http://127.0.0.1:8546"
avalanche = "https://avalanche-c-chain.publicnode.com"
arbitrum = "https://arbitrum-one.publicnode.com"
cronos = "https://evm.cronos.org"

View File

@@ -8,6 +8,12 @@ VITE_WALLETCONNECT_PROJECT_ID=your_reown_project_id_from_cloud_reown_com
VITE_THIRDWEB_CLIENT_ID=your_thirdweb_client_id_here
VITE_RPC_URL_138=https://rpc-http-pub.d-bis.org
# Z Chain (chainId 900002) — International Z Wallet
# VITE_ENABLE_Z_WALLET=true
# VITE_RPC_URL_900002=http://127.0.0.1:8546
# VITE_Z_CHAIN_EXPLORER_URL=https://explorer.z.omdnl.org
# VITE_Z_CHAIN_ID=900002
# Defi Oracle Meta Testnet (chain ID 2138) — off unless VITE_ENABLE_CHAIN2138 is true or 1
# VITE_ENABLE_CHAIN2138=true
# VITE_RPC_URL_2138=https://rpc.public-2138.defi-oracle.io

View File

@@ -0,0 +1,6 @@
# Z Ecosystem local dev
VITE_ENABLE_Z_WALLET=true
VITE_Z_ECOSYSTEM_URL=http://localhost:3002
VITE_RPC_URL_900002=http://127.0.0.1:8546
VITE_Z_CHAIN_EXPLORER_URL=https://explorer.zblockchainsystem.com
CHAIN_900002_RPC_URL=http://127.0.0.1:8546

View File

@@ -3,10 +3,13 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>OMNL Mission Control - Chain 138</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<meta name="theme-color" content="#141a16" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<title>DBIS — Chain 138</title>
<style>
html, body { margin: 0; min-height: 100%; background: #0f2847; color: #e8f4ff; }
html, body { margin: 0; min-height: 100%; background: #141a16; color: #eaecef; }
#root { min-height: 100vh; }
.omnl-boot {
min-height: 100vh;
@@ -14,26 +17,19 @@
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1rem;
gap: 0.75rem;
font-family: Inter, system-ui, sans-serif;
background:
radial-gradient(ellipse at 20% 0%, rgba(45, 107, 184, 0.45) 0%, transparent 55%),
linear-gradient(180deg, #0f2847 0%, #0a1a30 100%);
background: #141a16;
}
.omnl-boot__title {
font-size: 1.1rem;
letter-spacing: 0.12em;
text-transform: uppercase;
color: #00d4ff;
}
.omnl-boot__hint { color: #9bb8d4; font-size: 0.9rem; }
.omnl-boot__title { font-size: 1.125rem; font-weight: 600; color: #eaecef; }
.omnl-boot__hint { color: #848e9c; font-size: 0.875rem; }
</style>
</head>
<body>
<div id="root">
<div class="omnl-boot" role="status" aria-live="polite">
<div class="omnl-boot__title">OMNL Mission Control</div>
<div class="omnl-boot__hint">Loading dashboard...</div>
<div class="omnl-boot__title">DBIS</div>
<div class="omnl-boot__hint">Loading</div>
</div>
</div>
<script type="module" src="/src/main.tsx"></script>

View File

@@ -3,8 +3,15 @@
"version": "1.0.0",
"type": "module",
"scripts": {
"predev": "node scripts/sync-omnl-consoles.mjs",
"dev": "vite",
"prebuild": "node scripts/sync-omnl-consoles.mjs",
"build": "node --max-old-space-size=4096 ./node_modules/vite/bin/vite.js build",
"build:omnl": "node --max-old-space-size=4096 ./node_modules/vite/bin/vite.js build",
"build:z": "node scripts/build-z.mjs",
"prebuild:omnl": "node scripts/sync-omnl-consoles.mjs",
"prebuild:z": "node -e \"console.log('Z build - skipping OMNL console sync')\"",
"serve:portal": "node scripts/portal-serve.mjs",
"build:check": "tsc && NODE_OPTIONS='--max-old-space-size=4096' vite build",
"preview": "vite preview",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
@@ -15,6 +22,7 @@
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@lit/reactive-element": "^2.1.2",
"@safe-global/api-kit": "^4.0.1",
"@safe-global/protocol-kit": "^1.3.0",
"@tanstack/react-query": "^5.90.21",
@@ -52,6 +60,7 @@
"eslint-plugin-react-refresh": "^0.4.26",
"identity-obj-proxy": "^3.0.0",
"jsdom": "^23.2.0",
"serve": "^14.2.6",
"ts-jest": "^29.4.6",
"typescript": "^5.9.3",
"vite": "^5.4.21",

View File

@@ -0,0 +1,84 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="omnl-api-base" content="auto" />
<title>OMNL Compliance Console</title>
<link rel="stylesheet" href="/omnl/static/omnl-compliance-console.css" />
</head>
<body>
<div class="wrap">
<header>
<div>
<h1>OMNL Compliance Console</h1>
<p class="sub">HYBX · IPSAS / IFRS / US GAAP · Web3 notary &amp; reserve attestation · Chain 138</p>
</div>
<div class="toolbar">
<button type="button" id="btn-refresh">Refresh</button>
<button type="button" class="primary" id="btn-download-safe">Download Safe tx</button>
<button type="button" id="btn-toggle-raw">Raw JSON</button>
<a class="btn" href="/omnl/dashboard">Legacy dashboard</a>
<a class="btn" href="/api/v1/omnl/openapi.json">OpenAPI</a>
</div>
</header>
<div id="load-status" class="status-bar loading">Initializing…</div>
<p id="refreshed-at" class="sub" style="margin-top:-0.5rem"></p>
<section class="section">
<h2>Posture</h2>
<div id="posture-grid" class="grid"></div>
</section>
<section class="section">
<h2>Pending actions</h2>
<div id="pending-actions"></div>
</section>
<div class="grid" style="grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));">
<section class="section card">
<h2>On-chain evidence</h2>
<div id="evidence-kv"></div>
</section>
<section class="section card">
<h2>Gnosis Safe</h2>
<div id="safe-kv"></div>
</section>
<section class="section card">
<h2>External visibility</h2>
<div id="external-kv"></div>
</section>
</div>
<section class="section card">
<h2>HO nostro &amp; reserve coverage</h2>
<div id="ho-liquidity-kv"></div>
</section>
<section class="section card">
<h2>Triple-state reconcile</h2>
<div id="triple-summary"></div>
</section>
<section class="section card">
<h2>Sign-offs</h2>
<div id="signoffs-summary"></div>
</section>
<section id="raw-section" class="section hidden">
<h2>Full snapshot</h2>
<pre id="raw-json" class="raw">{}</pre>
</section>
<footer>
<p>
Auth: pass <code>?access_token=</code> when <code>OMNL_API_KEY</code> or <code>OMNL_DASHBOARD_TOKEN</code> is set.
API: <a href="/api/v1/omnl/compliance/console">/api/v1/omnl/compliance/console</a>
</p>
</footer>
</div>
<script src="/omnl/static/omnl-api-base.js"></script>
<script src="/omnl/static/omnl-compliance-console.js"></script>
</body>
</html>

View File

@@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>HYBX OMNL — status</title>
<style>
body { font-family: system-ui, sans-serif; margin: 1.5rem; max-width: 56rem; overflow-x: hidden; }
h1 { font-size: 1.25rem; }
pre { background: #111; color: #eee; padding: 1rem; overflow: auto; border-radius: 6px; font-size: 12px; -webkit-overflow-scrolling: touch; }
.err { color: #f66; }
a { color: #6cf; word-break: break-word; }
@media (max-width: 640px) {
body { margin: 1rem 0.75rem; }
h1 { font-size: 1.1rem; }
p { font-size: 0.9rem; }
}
</style>
</head>
<body>
<h1>OMNL API snapshot</h1>
<p><a href="/omnl/compliance"><strong>→ OMNL Compliance Console</strong></a> (posture, pending actions, Safe tx download)</p>
<p>Reads <code>/api/v1/omnl/ipsas/registry</code> and <code>/api/v1/omnl/ipsas/fineract-compare</code> (503 if Fineract env missing; 401 if <code>OMNL_API_KEY</code> is set and no <code>access_token</code> in this page URL).</p>
<p><a href="/api/v1/omnl/openapi.json">OpenAPI 3 JSON</a> · <a href="/api/v1/omnl/catalog">OMNL API catalog</a> · <a href="/api/v1/omnl/integration-status">integration status</a> · <a href="/api/v1/omnl/ipsas/registry">registry JSON</a> · <a href="/api/v1/omnl/ipsas/matrix">matrix JSON</a> · <a href="/api/v1/omnl/ipsas/fineract-health">Fineract health</a></p>
<h2>IPSAS registry</h2>
<pre id="reg">Loading…</pre>
<h2>Fineract compare</h2>
<pre id="fin">Loading…</pre>
<script src="/omnl/static/omnl-api-base.js"></script>
<script>
(function () {
const params = new URLSearchParams(window.location.search);
const accessToken = params.get('access_token') || '';
const apiBase = window.resolveOmnlApiBase ? window.resolveOmnlApiBase() : '/api/v1';
const apiHeaders = accessToken ? { Authorization: 'Bearer ' + accessToken } : {};
async function load() {
try {
const r = await fetch(apiBase + '/omnl/ipsas/registry');
document.getElementById('reg').textContent = JSON.stringify(await r.json(), null, 2);
} catch (e) {
document.getElementById('reg').innerHTML = '<span class="err">' + e + '</span>';
}
try {
const f = await fetch(apiBase + '/omnl/ipsas/fineract-compare', { headers: apiHeaders });
const t = await f.text();
document.getElementById('fin').textContent = t;
} catch (e) {
document.getElementById('fin').innerHTML = '<span class="err">' + e + '</span>';
}
}
load();
})();
</script>
</body>
</html>

View File

@@ -0,0 +1,8 @@
(function () {
/** auto → same-origin /api/v1 (vite proxy, portal-serve, or token-aggregation). */
window.resolveOmnlApiBase = function () {
const meta = document.querySelector('meta[name="omnl-api-base"]')?.getAttribute('content')?.trim();
if (meta && meta !== 'auto') return meta.replace(/\/$/, '');
return '/api/v1';
};
})();

View File

@@ -0,0 +1,219 @@
:root {
--bg: #0c0f14;
--panel: #141a22;
--border: #243044;
--text: #e8edf4;
--muted: #8b9cb3;
--ok: #3dd68c;
--warn: #f5c542;
--bad: #f07178;
--accent: #4ade80;
--mono: ui-monospace, 'Cascadia Code', 'SF Mono', monospace;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.45;
}
.wrap {
max-width: 72rem;
margin: 0 auto;
padding: 1.25rem 1rem 3rem;
}
header {
display: flex;
flex-wrap: wrap;
gap: 0.75rem 1.5rem;
align-items: baseline;
justify-content: space-between;
margin-bottom: 1.25rem;
}
h1 {
font-size: 1.35rem;
font-weight: 650;
margin: 0;
}
.sub {
color: var(--muted);
font-size: 0.9rem;
margin: 0.25rem 0 0;
}
.toolbar {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
align-items: center;
}
button, .btn {
appearance: none;
border: 1px solid var(--border);
background: var(--panel);
color: var(--text);
border-radius: 8px;
padding: 0.45rem 0.75rem;
font-size: 0.85rem;
cursor: pointer;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 0.35rem;
}
button:hover, .btn:hover { border-color: var(--accent); color: var(--accent); }
button.primary { background: #14532d; border-color: #22c55e; }
button.primary:hover { background: #166534; }
.grid {
display: grid;
gap: 1rem;
grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
margin-bottom: 1rem;
}
.card {
background: var(--panel);
border: 1px solid var(--border);
border-radius: 12px;
padding: 1rem;
}
.card h2 {
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--muted);
margin: 0 0 0.75rem;
}
.section { margin-bottom: 1.25rem; }
.section > h2 {
font-size: 1rem;
margin: 0 0 0.65rem;
}
.badge {
display: inline-block;
padding: 0.2rem 0.55rem;
border-radius: 999px;
font-size: 0.78rem;
font-weight: 600;
border: 1px solid transparent;
}
.badge.ok { background: #143528; color: var(--ok); border-color: #2a6b4a; }
.badge.warn { background: #3a3014; color: var(--warn); border-color: #6b5620; }
.badge.bad { background: #3a1c20; color: var(--bad); border-color: #6b3038; }
.badge.neutral { background: #1c2430; color: var(--muted); border-color: var(--border); }
.metric {
font-size: 1.1rem;
font-weight: 650;
}
.metric small {
display: block;
font-size: 0.78rem;
font-weight: 400;
color: var(--muted);
margin-top: 0.15rem;
}
.actions-list { list-style: none; padding: 0; margin: 0; }
.actions-list li {
border: 1px solid var(--border);
border-radius: 10px;
padding: 0.85rem 1rem;
margin-bottom: 0.65rem;
background: #10151c;
}
.actions-list h3 {
margin: 0 0 0.35rem;
font-size: 0.95rem;
}
.actions-list p {
margin: 0 0 0.5rem;
color: var(--muted);
font-size: 0.88rem;
}
.link-row { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.link-row a {
color: var(--accent);
font-size: 0.82rem;
text-decoration: none;
}
.link-row a:hover { text-decoration: underline; }
.kv {
display: grid;
grid-template-columns: minmax(8rem, 11rem) 1fr;
gap: 0.35rem 0.75rem;
font-size: 0.86rem;
}
.kv dt { color: var(--muted); margin: 0; }
.kv dd { margin: 0; font-family: var(--mono); font-size: 0.8rem; word-break: break-all; }
pre.raw {
background: #090c10;
border: 1px solid var(--border);
border-radius: 10px;
padding: 0.85rem;
overflow: auto;
font-size: 0.72rem;
line-height: 1.35;
max-height: 24rem;
}
.status-bar {
padding: 0.65rem 0.85rem;
border-radius: 8px;
margin-bottom: 1rem;
font-size: 0.88rem;
}
.status-bar.loading { background: #1a2430; color: var(--muted); }
.status-bar.error { background: #3a1c20; color: var(--bad); }
.status-bar.ok { background: #143528; color: var(--ok); }
.hidden { display: none !important; }
footer {
margin-top: 2rem;
color: var(--muted);
font-size: 0.8rem;
}
footer a { color: var(--accent); }
@media (max-width: 640px) {
.kv { grid-template-columns: 1fr; }
.wrap { padding-left: 0.75rem; padding-right: 0.75rem; }
header { flex-direction: column; align-items: stretch; }
.toolbar { width: 100%; }
.toolbar button,
.toolbar .btn {
min-height: 44px;
flex: 1 1 calc(50% - 0.25rem);
justify-content: center;
}
h1 { font-size: 1.15rem; }
pre, table { font-size: 12px; }
}

View File

@@ -0,0 +1,255 @@
(function () {
const params = new URLSearchParams(window.location.search);
const accessToken = params.get('access_token') || '';
const apiBase = window.resolveOmnlApiBase
? window.resolveOmnlApiBase()
: document.querySelector('meta[name="omnl-api-base"]')?.getAttribute('content')?.trim() || '/api/v1';
const els = {
status: document.getElementById('load-status'),
grid: document.getElementById('posture-grid'),
actions: document.getElementById('pending-actions'),
evidence: document.getElementById('evidence-kv'),
safe: document.getElementById('safe-kv'),
external: document.getElementById('external-kv'),
triple: document.getElementById('triple-summary'),
hoLiquidity: document.getElementById('ho-liquidity-kv'),
signoffs: document.getElementById('signoffs-summary'),
raw: document.getElementById('raw-json'),
refreshed: document.getElementById('refreshed-at'),
};
function apiHeaders() {
const h = { Accept: 'application/json' };
if (accessToken) h.Authorization = 'Bearer ' + accessToken;
return h;
}
function apiUrl(path) {
const base = apiBase.replace(/\/$/, '');
const rel = path.startsWith('/') ? path : '/' + path;
const u = new URL(base + rel, window.location.origin);
if (accessToken) u.searchParams.set('access_token', accessToken);
return u.toString();
}
function badge(ok, okLabel, badLabel) {
if (ok === true) return '<span class="badge ok">' + okLabel + '</span>';
if (ok === false) return '<span class="badge bad">' + badLabel + '</span>';
return '<span class="badge neutral">Unknown</span>';
}
function sevBadge(sev) {
const map = { critical: 'bad', high: 'bad', medium: 'warn', low: 'neutral' };
return '<span class="badge ' + (map[sev] || 'neutral') + '">' + sev + '</span>';
}
function renderPosture(data) {
const p = data.posture || {};
els.grid.innerHTML = [
card('Reporting', badge(p.reportingCompliant, 'Compliant', 'Not compliant'), ''),
card('Attestation', badge(!p.attestationStale, 'Fresh', 'Stale'), ''),
card('Triple reconcile', badge(p.tripleAligned, 'Aligned', 'Breaks'), ''),
card('Notary gate', badge(p.requireNotarizedEvidence, 'Enforced', 'Not enforced'), 'requireNotarizedEvidence'),
card('Threshold', '<div class="metric">' + (p.complianceThreshold || '—') + '</div>', 'IPSAS / IFRS / US GAAP'),
card('Policy v', '<div class="metric">' + (p.jurisdictionPolicyVersion ?? '—') + '</div>', 'ID jurisdiction'),
].join('');
}
function card(title, body, sub) {
return (
'<div class="card"><h2>' +
title +
'</h2>' +
body +
(sub ? '<small style="color:var(--muted);font-size:0.78rem">' + sub + '</small>' : '') +
'</div>'
);
}
function renderActions(actions) {
if (!actions || !actions.length) {
els.actions.innerHTML = '<p style="color:var(--muted)">No pending actions.</p>';
return;
}
els.actions.innerHTML =
'<ul class="actions-list">' +
actions
.map(function (a) {
const links = (a.links || [])
.map(function (l) {
const href = l.href.startsWith('http') ? l.href : apiUrl(l.href);
const dl = l.href.indexOf('safe-notary-gate-tx') >= 0 ? ' download="omnl-safe-notary-gate-tx.json"' : '';
return '<a href="' + href + '"' + dl + '>' + l.label + '</a>';
})
.join('');
return (
'<li><h3>' +
sevBadge(a.severity) +
' ' +
esc(a.title) +
'</h3><p>' +
esc(a.detail) +
'</p><div class="link-row">' +
links +
'</div></li>'
);
})
.join('') +
'</ul>';
}
function kv(rows) {
return (
'<dl class="kv">' +
rows
.map(function (r) {
return '<dt>' + esc(r[0]) + '</dt><dd>' + esc(String(r[1] ?? '—')) + '</dd>';
})
.join('') +
'</dl>'
);
}
function renderEvidence(data) {
const pr = data.proofReport;
const gate = data.onChainGate || {};
const triple = data.tripleReconcile;
els.evidence.innerHTML = kv([
['Evidence hash', pr && pr.evidenceHash],
['Merkle root', pr && pr.merkleRoot],
['Package 3-of-3', pr && pr.packageNotarized3of3],
['Reserve 3-of-3', pr && pr.reserveAttested3of3],
['Reserve store', gate.reserveStore],
['Notary registry', gate.notaryRegistry],
['Attestation threshold', gate.attestationThreshold],
['On-chain R', triple && triple.onChain && triple.onChain.r],
]);
}
function renderSafe(data) {
const pr = data.proofReport;
const g = (pr && pr.gnosisSafe) || {};
const sw = data.safeWallet || {};
els.safe.innerHTML = kv([
['Admin Safe', g.address || data.web3.deployed.GnosisSafeAdmin],
['Threshold', g.threshold ? g.threshold + '-of-' + g.owners : '—'],
['Safe Wallet registry', sw.npmRegistryHas138 ? 'chain 138 listed' : 'pending PR #1568'],
['Registry status', sw.status],
]);
}
function renderExternal(data) {
const ev = data.externalVisibility || {};
const guosmm = ev.guosmmGazette || {};
const noticeLinks = (guosmm.notices || [])
.map(function (n) {
return '<a href="' + esc(n.url) + '" target="_blank" rel="noopener">' + esc(n.documentId) + '</a>';
})
.join(' · ');
els.external.innerHTML =
kv([
['GUOSMM gazette', guosmm.base || '—'],
['API registry', guosmm.apiRegistry || '/api/v1/omnl/gazette/notices'],
['Notices', noticeLinks || '—'],
['DefiLlama chain', ev.defiLlama && ev.defiLlama.chainPage],
['Pricing PR', ev.defiLlama && ev.defiLlama.pr12094],
['Bridge TVL PR', ev.defiLlama && ev.defiLlama.pr19451],
['Safe deployments PR', ev.safeDeployments && ev.safeDeployments.pr1568],
]);
}
function renderHoLiquidity(snap) {
if (!els.hoLiquidity) return;
if (!snap) {
els.hoLiquidity.textContent = 'HO liquidity snapshot unavailable.';
return;
}
const cash = snap.cashInBank || {};
const cov = snap.reserveCoverage || {};
els.hoLiquidity.innerHTML = kv([
['Generated', snap.generatedAt],
['Nostro cash (GL 13010)', cash.cashInBankUsd != null ? '$' + Number(cash.cashInBankUsd).toLocaleString() : '—'],
['Reserve coverage', cov.coverageRatio != null ? cov.coverageRatio.toFixed(2) + '×' : '—'],
['Reserve assets', cov.reserveAssetsUsd != null ? '$' + Number(cov.reserveAssetsUsd).toLocaleString() : '—'],
['Issued c* USD', cov.issuedCStarUsd != null ? '$' + Number(cov.issuedCStarUsd).toLocaleString() : '—'],
['Public API', apiBase + '/omnl/ho-liquidity-snapshot'],
]);
}
function renderTriple(data) {
const t = data.tripleReconcile;
if (!t) {
els.triple.textContent = 'Triple-state reconcile unavailable.';
return;
}
const breaks = (t.breaks || []).length;
els.triple.innerHTML =
badge(t.aligned, 'Aligned', breaks + ' break(s)') +
' <span style="color:var(--muted);margin-left:0.5rem;font-size:0.88rem">line ' +
esc(t.lineId).slice(0, 18) +
'…</span>';
}
function renderSignoffs(data) {
const s = data.signoffs || {};
els.signoffs.innerHTML = '<pre class="raw" style="max-height:10rem">' + esc(JSON.stringify(s, null, 2)) + '</pre>';
}
function esc(s) {
return String(s)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
function setStatus(kind, msg) {
els.status.className = 'status-bar ' + kind;
els.status.textContent = msg;
}
async function load() {
setStatus('loading', 'Loading compliance console…');
try {
const res = await fetch(apiUrl('/omnl/compliance/console'), { headers: apiHeaders() });
const text = await res.text();
let data;
try {
data = JSON.parse(text);
} catch {
throw new Error(res.status + ' — non-JSON response');
}
if (!res.ok) throw new Error(data.error || res.statusText);
renderPosture(data);
renderActions(data.pendingActions);
renderEvidence(data);
renderSafe(data);
renderExternal(data);
renderTriple(data);
try {
const hoRes = await fetch(apiUrl('/omnl/ho-liquidity-snapshot'), { headers: apiHeaders() });
if (hoRes.ok) renderHoLiquidity(await hoRes.json());
} catch (_) {
renderHoLiquidity(null);
}
renderSignoffs(data);
els.raw.textContent = JSON.stringify(data, null, 2);
els.refreshed.textContent = 'Updated ' + new Date(data.generatedAt).toLocaleString();
setStatus('ok', 'Live — ' + data.pendingActions.length + ' pending action(s)');
} catch (e) {
setStatus('error', 'Failed to load: ' + (e && e.message ? e.message : e));
}
}
document.getElementById('btn-refresh').addEventListener('click', load);
document.getElementById('btn-download-safe').addEventListener('click', function () {
window.location.href = apiUrl('/omnl/compliance/safe-notary-gate-tx');
});
document.getElementById('btn-toggle-raw').addEventListener('click', function () {
document.getElementById('raw-section').classList.toggle('hidden');
});
load();
})();

View File

@@ -0,0 +1,190 @@
:root {
--font-mono: "Consolas", "Lucida Console", "Courier New", monospace;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
font-family: system-ui, sans-serif;
}
body.theme-black {
background: #0a0a0a;
color: #33ff66;
}
body.theme-color {
background: #d8d8d8;
color: #111;
}
.layout {
max-width: 1200px;
margin: 0 auto;
padding: 1rem 1.25rem 2rem;
}
.topbar {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 1rem;
align-items: flex-start;
margin-bottom: 1rem;
}
h1 {
font-size: 1.15rem;
margin: 0 0 0.25rem;
}
.sub {
margin: 0;
opacity: 0.85;
font-size: 0.85rem;
}
.toolbar {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
button,
.btn {
font: inherit;
cursor: pointer;
border: 1px solid currentColor;
background: transparent;
color: inherit;
padding: 0.35rem 0.65rem;
border-radius: 4px;
text-decoration: none;
font-size: 0.85rem;
}
button.active {
font-weight: 700;
}
.card {
border: 1px solid currentColor;
border-radius: 6px;
padding: 0.75rem;
margin-bottom: 1rem;
}
.params {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
gap: 0.75rem;
}
.params label {
display: flex;
flex-direction: column;
gap: 0.25rem;
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.params input {
font: inherit;
padding: 0.35rem 0.5rem;
border: 1px solid currentColor;
background: transparent;
color: inherit;
border-radius: 4px;
}
.main {
display: grid;
grid-template-columns: 1fr min(22rem, 100%);
gap: 1rem;
}
@media (max-width: 900px) {
.main {
grid-template-columns: 1fr;
}
}
.terminal {
margin: 0;
min-height: 28rem;
max-height: 70vh;
overflow: auto;
font-family: var(--font-mono);
font-size: 0.78rem;
line-height: 1.35;
white-space: pre-wrap;
word-break: break-word;
}
body.theme-black .terminal {
background: #000;
color: #33ff66;
padding: 1rem;
}
body.theme-color .terminal {
background: #ececec;
color: #111;
padding: 1rem;
}
.side h2 {
font-size: 0.85rem;
margin: 0 0 0.5rem;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.cmd-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.35rem;
margin-bottom: 0.75rem;
}
.alchemy-row {
display: flex;
flex-direction: column;
gap: 0.25rem;
font-size: 0.75rem;
margin-bottom: 0.75rem;
}
.alchemy-row input {
font: inherit;
padding: 0.35rem;
border: 1px solid currentColor;
background: transparent;
color: inherit;
border-radius: 4px;
}
.json-out {
margin: 0;
max-height: 16rem;
overflow: auto;
font-family: var(--font-mono);
font-size: 0.7rem;
white-space: pre-wrap;
word-break: break-word;
opacity: 0.9;
}
footer {
font-size: 0.75rem;
opacity: 0.8;
}
footer code {
font-family: var(--font-mono);
}

View File

@@ -0,0 +1,219 @@
(function () {
const API_BASE = window.resolveOmnlApiBase
? window.resolveOmnlApiBase()
: document.querySelector('meta[name="omnl-api-base"]')?.getAttribute('content') || '/api/v1';
const screenEl = document.getElementById('terminal-screen');
const jsonEl = document.getElementById('json-out');
const inpRef = document.getElementById('inp-settlement-ref');
const inpOffice = document.getElementById('inp-office-id');
const inpDate = document.getElementById('inp-value-date');
const inpAmount = document.getElementById('inp-amount');
const inpToken = document.getElementById('inp-token');
const inpAlchemyAddr = document.getElementById('inp-alchemy-addr');
let screenMode = 'black';
const inpConnection = document.getElementById('inp-connection');
const DEFAULT_CONNECTION = 'mk-albert-gate-limited';
const DEFAULT_WALLET = '0xb3B416BdE671c256aAbFB56358baD91D46BB9c39';
(function initFromQuery() {
const p = new URLSearchParams(window.location.search);
const conn = p.get('connectionId') || DEFAULT_CONNECTION;
if (inpConnection) inpConnection.value = conn;
const ref = p.get('settlementRef');
if (ref) inpRef.value = ref;
})();
function qs() {
const p = new URLSearchParams(window.location.search);
const token = inpToken.value.trim() || p.get('access_token') || '';
const q = new URLSearchParams({
settlementRef: inpRef.value.trim(),
officeId: String(inpOffice.value || '1'),
valueDate: inpDate.value,
amountUsd: inpAmount.value.trim(),
});
const conn = inpConnection && inpConnection.value ? inpConnection.value.trim() : '';
if (conn) q.set('connectionId', conn);
if (token) q.set('access_token', token);
return q;
}
function authHeaders() {
const token = inpToken.value.trim() || new URLSearchParams(window.location.search).get('access_token') || '';
return token ? { Authorization: 'Bearer ' + token } : {};
}
async function apiGet(path, extra) {
const q = qs();
if (extra) {
Object.entries(extra).forEach(function (kv) {
q.set(kv[0], kv[1]);
});
}
const res = await fetch(API_BASE + path + '?' + q.toString(), { headers: authHeaders() });
const text = await res.text();
let data;
try {
data = JSON.parse(text);
} catch {
data = { raw: text, status: res.status };
}
if (!res.ok) throw new Error(data.error || data.raw || res.statusText);
return data;
}
async function apiPost(path, body) {
const q = qs();
const res = await fetch(API_BASE + path + '?' + q.toString(), {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders() },
body: JSON.stringify(body),
});
const data = await res.json().catch(function () {
return {};
});
if (!res.ok) throw new Error(data.error || res.statusText);
return data;
}
function showJson(data) {
jsonEl.textContent = JSON.stringify(data, null, 2);
}
function showScreen(text) {
screenEl.textContent = text;
}
function setTheme(mode) {
screenMode = mode === 'color' ? 'color' : 'black';
document.body.classList.toggle('theme-black', screenMode === 'black');
document.body.classList.toggle('theme-color', screenMode === 'color');
document.getElementById('btn-mode-black').classList.toggle('active', screenMode === 'black');
document.getElementById('btn-mode-color').classList.toggle('active', screenMode === 'color');
}
async function loadScreen(artifact) {
const data = await apiGet('/omnl/terminal/screen', {
mode: screenMode,
artifact: artifact || 'package',
format: 'json',
});
showScreen(data.text || '');
showJson(data);
}
async function runCmd(cmd) {
try {
if (cmd === 'help') {
showScreen(
[
'OMNL / HYBX SETTLEMENT TERMINAL — COMMANDS',
'',
' status — integration status',
' package — full JSON package',
' pof — proof of funds',
' debit-note — debit note',
' remittance — remittance advice',
' token-san — tokenization sanitized copy',
' swift-copy — conversion SWIFT copy',
' screen — terminal screen (current theme)',
' alchemy balance — eth_getBalance via Alchemy',
'',
'Toggle Black/Green vs Gray/Black with toolbar buttons.',
].join('\n')
);
return;
}
if (cmd === 'status') {
const q = qs();
const token = q.get('access_token');
const url = API_BASE + '/omnl/terminal/status' + (token ? '?access_token=' + encodeURIComponent(token) : '');
const data = await fetch(url, { headers: authHeaders() }).then(function (r) {
return r.json();
});
showJson(data);
showScreen(JSON.stringify(data, null, 2));
return;
}
if (cmd === 'package') {
const data = await apiGet('/omnl/terminal/package');
showJson(data);
if (data.artifacts && data.artifacts.screens) {
showScreen(data.artifacts.screens[screenMode] || '');
}
return;
}
if (cmd === 'pof') {
const data = await apiGet('/omnl/terminal/proof-of-funds');
showJson(data);
await loadScreen('proof-of-funds');
return;
}
if (cmd === 'debit') {
const data = await apiGet('/omnl/terminal/debit-note');
showJson(data);
await loadScreen('debit-note');
return;
}
if (cmd === 'remittance') {
const data = await apiGet('/omnl/terminal/remittance-advice');
showJson(data);
await loadScreen('remittance-advice');
return;
}
if (cmd === 'token') {
const data = await apiGet('/omnl/terminal/tokenization-sanitized');
showJson(data);
await loadScreen('tokenization-sanitized');
return;
}
if (cmd === 'swift') {
const data = await apiGet('/omnl/terminal/conversion-swift-copy');
showJson(data);
await loadScreen('conversion-swift-copy');
return;
}
if (cmd === 'screen') {
await loadScreen('package');
return;
}
if (cmd === 'alchemy-balance') {
const addr = (inpAlchemyAddr.value || '').trim();
if (!addr) throw new Error('Set Alchemy address first');
const data = await apiPost('/omnl/terminal/alchemy/rpc', {
network: 'eth-mainnet',
method: 'eth_getBalance',
params: [addr, 'latest'],
connectionId: (inpConnection && inpConnection.value) || DEFAULT_CONNECTION,
});
showJson(data);
showScreen('ALCHEMY eth_getBalance\nADDRESS: ' + addr + '\nRESULT: ' + data.result);
return;
}
} catch (e) {
showScreen('ERROR: ' + (e && e.message ? e.message : String(e)));
}
}
document.getElementById('btn-mode-black').addEventListener('click', function () {
setTheme('black');
runCmd('screen');
});
document.getElementById('btn-mode-color').addEventListener('click', function () {
setTheme('color');
runCmd('screen');
});
document.getElementById('btn-refresh').addEventListener('click', function () {
runCmd('screen');
});
document.querySelectorAll('[data-cmd]').forEach(function (btn) {
btn.addEventListener('click', function () {
runCmd(btn.getAttribute('data-cmd'));
});
});
setTheme('black');
runCmd('screen');
})();

View File

@@ -0,0 +1,76 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="omnl-api-base" content="auto" />
<title>OMNL / HYBX Settlement Terminal</title>
<link rel="stylesheet" href="/omnl/static/omnl-settlement-terminal.css" />
</head>
<body class="theme-black">
<div class="layout">
<header class="topbar">
<div>
<h1>OMNL / HYBX Settlement Terminal</h1>
<p class="sub">Fineract SoR · Alchemy on-chain · institutional document API</p>
</div>
<div class="toolbar">
<button type="button" id="btn-mode-black" class="active">Black / Green</button>
<button type="button" id="btn-mode-color">Gray / Black</button>
<button type="button" id="btn-refresh">Refresh</button>
<a class="btn" href="/omnl/compliance">Compliance</a>
<a class="btn" href="/api/v1/omnl/terminal/status">Status JSON</a>
</div>
</header>
<section class="params card">
<label>Connection
<select id="inp-connection">
<option value="mk-albert-gate-limited" selected>MK Albert Gate Limited</option>
<option value="">(none — legacy defaults)</option>
</select>
</label>
<label>Settlement ref <input id="inp-settlement-ref" type="text" value="MK-ALBERT-GATE-20260529" /></label>
<label>Office ID <input id="inp-office-id" type="number" value="1" min="1" /></label>
<label>Value date <input id="inp-value-date" type="date" value="2026-03-17" /></label>
<label>Amount USD <input id="inp-amount" type="text" value="1000000000.00" /></label>
<label>API token <input id="inp-token" type="password" placeholder="OMNL_API_KEY (optional)" autocomplete="off" /></label>
</section>
<div class="main">
<section class="screen-wrap card">
<pre id="terminal-screen" class="terminal" aria-live="polite">Loading…</pre>
</section>
<aside class="side card">
<h2>Commands</h2>
<div class="cmd-grid">
<button type="button" data-cmd="help">help</button>
<button type="button" data-cmd="status">status</button>
<button type="button" data-cmd="package">package</button>
<button type="button" data-cmd="pof">pof</button>
<button type="button" data-cmd="debit">debit-note</button>
<button type="button" data-cmd="remittance">remittance</button>
<button type="button" data-cmd="token">token-san</button>
<button type="button" data-cmd="swift">swift-copy</button>
<button type="button" data-cmd="screen">screen</button>
<button type="button" data-cmd="alchemy-balance">alchemy balance</button>
</div>
<label>Alchemy address
<input id="inp-alchemy-addr" type="text" value="0xb3B416BdE671c256aAbFB56358baD91D46BB9c39" />
</label>
<h2>Output</h2>
<pre id="json-out" class="json-out">{}</pre>
</aside>
</div>
<footer>
<p>
API: <code>/api/v1/omnl/terminal/*</code> · Documents bound to Fineract — not SWIFT NET transmission.
Pass <code>?access_token=</code> or Bearer when <code>OMNL_API_KEY</code> is set.
</p>
</footer>
</div>
<script src="/omnl/static/omnl-api-base.js"></script>
<script src="/omnl/static/omnl-settlement-terminal.js"></script>
</body>
</html>

View File

@@ -0,0 +1,16 @@
import { spawnSync } from 'node:child_process';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
const viteBin = path.join(root, 'node_modules', 'vite', 'bin', 'vite.js');
process.env.VITE_ECOSYSTEM = 'z';
const result = spawnSync(
process.execPath,
['--max-old-space-size=4096', viteBin, 'build'],
{ stdio: 'inherit', env: process.env, cwd: root },
);
process.exit(result.status ?? 1);

View File

@@ -0,0 +1,142 @@
/**
* Production-style portal server: static dist + API proxies (matches vite.config.ts).
*/
import http from 'http';
import https from 'https';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const DIST = path.join(__dirname, '..', 'dist');
const PORT = Number(process.env.PORT || process.env.OMNL_BANK_FRONTEND_PORT || 3002);
const API_TARGET = process.env.TOKEN_AGGREGATION_URL || 'http://127.0.0.1:3000';
const SETTLEMENT_TARGET = process.env.SETTLEMENT_URL || process.env.SETTLEMENT_MIDDLEWARE_URL || 'http://127.0.0.1:3011';
const EXCHANGE_TARGET = process.env.EXCHANGE_URL || '';
const MIME = {
'.html': 'text/html; charset=utf-8',
'.js': 'application/javascript; charset=utf-8',
'.css': 'text/css; charset=utf-8',
'.json': 'application/json; charset=utf-8',
'.svg': 'image/svg+xml',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.webp': 'image/webp',
'.ico': 'image/x-icon',
'.woff': 'font/woff',
'.woff2': 'font/woff2',
'.ttf': 'font/ttf',
'.map': 'application/json',
};
function shouldServeOmnlStatic(url) {
return (
/^\/omnl\/(compliance|dashboard|terminal)\/?(\?.*)?$/.test(url) ||
url.startsWith('/omnl/static/')
);
}
function proxyRequest(req, res, targetBase, rewrite) {
const incoming = new URL(req.url, 'http://localhost');
const target = new URL(targetBase);
const pathname = rewrite ? rewrite(incoming.pathname) : incoming.pathname;
const options = {
protocol: target.protocol,
hostname: target.hostname,
port: target.port || (target.protocol === 'https:' ? 443 : 80),
path: `${pathname}${incoming.search}`,
method: req.method,
headers: { ...req.headers, host: target.host },
};
const lib = target.protocol === 'https:' ? https : http;
const proxyReq = lib.request(options, (proxyRes) => {
res.writeHead(proxyRes.statusCode ?? 502, proxyRes.headers);
proxyRes.pipe(res);
});
proxyReq.on('error', (err) => {
if (!res.headersSent) {
res.writeHead(502, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'proxy_error', message: err.message }));
}
});
req.pipe(proxyReq);
}
function sendFile(res, filePath, cacheControl) {
const ext = path.extname(filePath).toLowerCase();
const type = MIME[ext] || 'application/octet-stream';
const headers = { 'Content-Type': type };
if (cacheControl) headers['Cache-Control'] = cacheControl;
res.writeHead(200, headers);
fs.createReadStream(filePath).pipe(res);
}
function resolveStatic(urlPath) {
const decoded = decodeURIComponent(urlPath.split('?')[0]);
if (decoded.endsWith('/')) {
const indexPath = path.join(DIST, decoded, 'index.html');
if (fs.existsSync(indexPath)) return indexPath;
}
const direct = path.join(DIST, decoded);
if (fs.existsSync(direct) && fs.statSync(direct).isFile()) return direct;
const withIndex = path.join(DIST, decoded, 'index.html');
if (fs.existsSync(withIndex)) return withIndex;
return null;
}
const server = http.createServer((req, res) => {
const url = req.url || '/';
if (url.startsWith('/api')) {
return proxyRequest(req, res, API_TARGET);
}
if (url.startsWith('/reserve')) {
return proxyRequest(req, res, API_TARGET);
}
if (url.startsWith('/settlement')) {
return proxyRequest(req, res, SETTLEMENT_TARGET, (pathname) =>
pathname.replace(/^\/settlement/, '/api/v1/settlement'),
);
}
if (url.startsWith('/exchange')) {
if (!EXCHANGE_TARGET) {
res.writeHead(404, { 'Content-Type': 'application/json' });
return res.end(JSON.stringify({ error: 'not_available', message: 'Z Ecosystem only' }));
}
return proxyRequest(req, res, EXCHANGE_TARGET);
}
if (url.startsWith('/omnl/')) {
res.writeHead(404, { 'Content-Type': 'application/json' });
return res.end(JSON.stringify({ error: 'not_available', message: 'Z Ecosystem only' }));
}
const file = resolveStatic(url === '/' ? '/index.html' : url);
if (file) {
const cache =
url.startsWith('/assets/') ? 'public, max-age=31536000, immutable' : 'no-cache, no-store, must-revalidate';
return sendFile(res, file, cache);
}
const indexHtml = path.join(DIST, 'index.html');
if (fs.existsSync(indexHtml)) {
return sendFile(res, indexHtml, 'no-cache, no-store, must-revalidate');
}
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not found');
});
if (!fs.existsSync(DIST)) {
console.error(`Missing build output: ${DIST}\nRun: npm run build`);
process.exit(1);
}
server.listen(PORT, () => {
console.log(`portal-serve listening on http://localhost:${PORT}`);
console.log(` dist: ${DIST}`);
console.log(` api: ${API_TARGET}`);
console.log(` settlement: ${SETTLEMENT_TARGET}`);
});

View File

@@ -0,0 +1,73 @@
#!/usr/bin/env node
/**
* Copy OMNL operator console static assets into frontend-dapp/public for Vite + portal-serve.
*/
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const frontendRoot = path.resolve(__dirname, '..');
const srcPublic = path.resolve(frontendRoot, '../services/token-aggregation/public');
const destRoot = path.join(frontendRoot, 'public', 'omnl');
const staticFiles = [
'omnl-api-base.js',
'omnl-compliance-console.css',
'omnl-compliance-console.js',
'omnl-settlement-terminal.css',
'omnl-settlement-terminal.js',
];
const pageMap = [
{ src: 'omnl-compliance-console.html', dest: 'compliance/index.html' },
{ src: 'omnl-dashboard.html', dest: 'dashboard/index.html' },
{ src: 'omnl-settlement-terminal.html', dest: 'terminal/index.html' },
];
function rmrf(dir) {
if (!fs.existsSync(dir)) return;
for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
const p = path.join(dir, ent.name);
if (ent.isDirectory()) rmrf(p);
else fs.unlinkSync(p);
}
fs.rmdirSync(dir);
}
function ensureDir(dir) {
fs.mkdirSync(dir, { recursive: true });
}
function copyFile(src, dest) {
ensureDir(path.dirname(dest));
fs.copyFileSync(src, dest);
}
if (!fs.existsSync(srcPublic)) {
console.warn('[sync-omnl-consoles] token-aggregation public missing — skip');
process.exit(0);
}
if (fs.existsSync(destRoot)) rmrf(destRoot);
ensureDir(path.join(destRoot, 'static'));
for (const name of staticFiles) {
const src = path.join(srcPublic, name);
if (!fs.existsSync(src)) {
console.warn(`[sync-omnl-consoles] missing ${name}`);
continue;
}
copyFile(src, path.join(destRoot, 'static', name));
}
for (const { src, dest } of pageMap) {
const from = path.join(srcPublic, src);
if (!fs.existsSync(from)) {
console.warn(`[sync-omnl-consoles] missing ${src}`);
continue;
}
copyFile(from, path.join(destRoot, dest));
}
console.log('[sync-omnl-consoles] synced operator consoles → public/omnl/');

View File

@@ -1,4 +1,5 @@
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
import { useEffect } from 'react'
import { WagmiProvider } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ThirdwebProvider } from '@thirdweb-dev/react'
@@ -14,11 +15,15 @@ import ReservePage from './pages/ReservePage'
import HistoryPage from './pages/HistoryPage'
import AdminPanel from './pages/AdminPanel'
import DocsPage from './pages/DocsPage'
import WalletsDemoPage from './pages/WalletsDemoPage'
import HubPage from './pages/HubPage'
import WalletsDemoPage from './pages/WalletsDemoPage'
import ZEcosystemHubPage from './pages/ZEcosystemHubPage'
import ZWalletPage from './pages/ZWalletPage'
import Layout from './components/layout/Layout'
import OmnlProductLayout from './components/layout/OmnlProductLayout'
import ZProductLayout from './components/layout/ZProductLayout'
import ToastProvider from './components/ui/ToastProvider'
import { isZEcosystem, isOmnlEcosystem } from './config/ecosystemMode'
const queryClient = new QueryClient({
defaultOptions: {
@@ -35,6 +40,65 @@ const queryClient = new QueryClient({
})
const THIRDWEB_CLIENT_ID = import.meta.env.VITE_THIRDWEB_CLIENT_ID || '542981292d51ec610388ba8985f027d7'
function ExternalRedirect({ to }: { to: string }) {
useEffect(() => {
window.location.replace(to)
}, [to])
return null
}
function ZRoutes() {
return (
<Routes>
<Route
element={
<ThirdwebProvider clientId={THIRDWEB_CLIENT_ID}>
<ZProductLayout />
</ThirdwebProvider>
}
>
<Route path="/z" element={<ZEcosystemHubPage />} />
<Route path="/z-wallet" element={<ZWalletPage />} />
<Route path="/swap" element={<SwapPage />} />
<Route path="/central-bank" element={<CentralBankPage />} />
</Route>
<Route path="/" element={<Navigate to="/z" replace />} />
<Route path="*" element={<Navigate to="/z" replace />} />
</Routes>
)
}
function OmnlRoutes() {
return (
<Routes>
<Route element={<OmnlProductLayout />}>
<Route path="/hub" element={<HubPage />} />
<Route path="/central-bank" element={<CentralBankPage />} />
<Route path="/office-24" element={<Office24Page />} />
<Route path="/trade" element={<TradePage />} />
<Route path="/swap" element={<SwapPage />} />
</Route>
<Route
element={
<ThirdwebProvider clientId={THIRDWEB_CLIENT_ID}>
<Layout />
</ThirdwebProvider>
}
>
<Route path="/bridge" element={<BridgePage />} />
<Route path="/reserve" element={<ReservePage />} />
<Route path="/history" element={<HistoryPage />} />
<Route path="/admin" element={<AdminPanel />} />
<Route path="/docs" element={<DocsPage />} />
<Route path="/wallets" element={<WalletsDemoPage />} />
</Route>
<Route path="/z" element={<ExternalRedirect to="https://zblockchainsystem.com/z" />} />
<Route path="/z-wallet" element={<ExternalRedirect to="https://zblockchainsystem.com/z-wallet" />} />
<Route path="/" element={<Navigate to="/hub" replace />} />
</Routes>
)
}
function App() {
return (
<ErrorBoundary>
@@ -48,30 +112,7 @@ function App() {
>
<ToastProvider />
<AdminProvider>
<Routes>
<Route element={<OmnlProductLayout />}>
<Route path="/hub" element={<HubPage />} />
<Route path="/central-bank" element={<CentralBankPage />} />
<Route path="/office-24" element={<Office24Page />} />
<Route path="/trade" element={<TradePage />} />
<Route path="/swap" element={<SwapPage />} />
</Route>
<Route
element={
<ThirdwebProvider clientId={THIRDWEB_CLIENT_ID}>
<Layout />
</ThirdwebProvider>
}
>
<Route path="/" element={<BridgePage />} />
<Route path="/bridge" element={<BridgePage />} />
<Route path="/reserve" element={<ReservePage />} />
<Route path="/history" element={<HistoryPage />} />
<Route path="/admin" element={<AdminPanel />} />
<Route path="/docs" element={<DocsPage />} />
<Route path="/wallets" element={<WalletsDemoPage />} />
</Route>
</Routes>
{isZEcosystem ? <ZRoutes /> : isOmnlEcosystem ? <OmnlRoutes /> : <OmnlRoutes />}
</AdminProvider>
</BrowserRouter>
</QueryClientProvider>

View File

@@ -99,7 +99,7 @@ export default function AdminDashboard() {
}, [publicClient, address])
return (
<div className="space-y-6 nasa-mission-root">
<div className="space-y-6 app-shell">
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<DashboardStatCard label="Total actions" value={String(stats.totalActions)} accent="blue" icon="admin" />
<DashboardStatCard label="Pending" value={String(stats.pending)} accent="gold" icon="alert" />

View File

@@ -73,7 +73,7 @@ export default function AuditLogViewer() {
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search logs..."
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500"
/>
<div className="flex gap-2">
{(['all', 'success', 'failure'] as const).map((f) => (

View File

@@ -147,7 +147,7 @@ export default function FunctionPermissions() {
<select
value={selectedContract}
onChange={(e) => setSelectedContract(e.target.value)}
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:border-blue-500"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:border-green-500"
>
<option value={CONTRACT_ADDRESSES.mainnet.MAINNET_TETHER}>MainnetTether</option>
<option value={CONTRACT_ADDRESSES.mainnet.TRANSACTION_MIRROR}>TransactionMirror</option>
@@ -227,7 +227,7 @@ export default function FunctionPermissions() {
<select
value={selectedRole}
onChange={(e) => setSelectedRole(e.target.value)}
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:border-blue-500"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:border-green-500"
>
{roles.map((role) => (
<option key={role.id} value={role.id}>
@@ -239,7 +239,7 @@ export default function FunctionPermissions() {
<div className="flex-1">
<label className="block text-white/70 text-sm mb-2">Function</label>
<select
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:border-blue-500"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:border-green-500"
>
{contractFunctions.map((func) => (
<option key={func} value={func}>

View File

@@ -92,7 +92,7 @@ export default function GasOptimizer() {
value={estimatedGas}
onChange={(e) => setEstimatedGas(e.target.value)}
placeholder="21000"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500"
/>
</div>

View File

@@ -105,7 +105,7 @@ export default function ImpersonationMode() {
value={inputAddress}
onChange={(e) => setInputAddress(e.target.value)}
placeholder="0x... or name.eth"
className="flex-1 px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500"
className="flex-1 px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500"
onKeyPress={(e) => {
if (e.key === 'Enter') {
handleEnableImpersonation()

View File

@@ -177,7 +177,7 @@ export default function MainnetTetherAdmin() {
value={newAdmin}
onChange={(e) => setNewAdmin(e.target.value)}
placeholder="0x..."
className="flex-1 px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500"
className="flex-1 px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500"
/>
<button
onClick={handleSetAdmin}
@@ -200,7 +200,7 @@ export default function MainnetTetherAdmin() {
value={blockNumber}
onChange={(e) => setBlockNumber(e.target.value)}
placeholder="Block number"
className="flex-1 px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500"
className="flex-1 px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500"
/>
<button
onClick={handleCheckBlock}

View File

@@ -72,7 +72,7 @@ export default function MultiChainAdmin() {
<select
value={selectedChain}
onChange={(e) => setSelectedChain(parseInt(e.target.value))}
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:border-blue-500"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:border-green-500"
>
{CHAIN_CONFIGS.map((chain) => (
<option key={chain.chainId} value={chain.chainId}>

View File

@@ -182,7 +182,7 @@ export default function MultiSigAdmin() {
<select
value={selectedContract}
onChange={(e) => setSelectedContract(e.target.value)}
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:border-blue-500"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:border-green-500"
>
<option value={contractAddress}>MainnetTether ({contractAddress.slice(0, 10)}...)</option>
<option value={CONTRACT_ADDRESSES.mainnet.TRANSACTION_MIRROR}>

View File

@@ -145,7 +145,7 @@ export default function OwnerManagement() {
<select
value={selectedWallet}
onChange={(e) => setSelectedWallet(e.target.value)}
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:border-blue-500"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:border-green-500"
>
<option value="">Select a wallet...</option>
{wallets.map((wallet) => (
@@ -189,14 +189,14 @@ export default function OwnerManagement() {
value={newOwner}
onChange={(e) => setNewOwner(e.target.value)}
placeholder="0x..."
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500 font-mono text-sm"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500 font-mono text-sm"
/>
<input
type="text"
value={ownerLabel}
onChange={(e) => setOwnerLabel(e.target.value)}
placeholder="Owner label (optional)"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500"
/>
<button
onClick={addOwner}

View File

@@ -134,7 +134,7 @@ export default function Pagination({
<select
value={itemsPerPage}
onChange={(e) => onItemsPerPageChange(Number(e.target.value))}
className="px-3 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:border-blue-500"
className="px-3 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:border-green-500"
>
<option value={10}>10</option>
<option value={25}>25</option>

View File

@@ -151,7 +151,7 @@ export default function RealtimeMonitor() {
onChange={(e) => setWsUrl(e.target.value)}
placeholder="wss://your-monitoring-server.com/ws"
disabled={monitoring}
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500 font-mono text-sm disabled:opacity-50"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500 font-mono text-sm disabled:opacity-50"
/>
<p className="text-white/60 text-xs mt-1">
Leave empty to use polling fallback (checks every 10 seconds)

View File

@@ -139,12 +139,12 @@ export default function RoleBasedAccess() {
value={selectedAddress}
onChange={(e) => setSelectedAddress(e.target.value)}
placeholder="0x..."
className="flex-1 px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500 font-mono text-sm"
className="flex-1 px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500 font-mono text-sm"
/>
<select
value={selectedRole}
onChange={(e) => setSelectedRole(e.target.value as AdminRole)}
className="px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:border-blue-500"
className="px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:border-green-500"
>
<option value={AdminRole.SUPER_ADMIN}>Super Admin</option>
<option value={AdminRole.OPERATOR}>Operator</option>

View File

@@ -153,7 +153,7 @@ export default function ScheduledActions() {
value={newAction.name}
onChange={(e) => setNewAction({ ...newAction, name: e.target.value })}
placeholder="Daily State Check"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500"
/>
</div>
@@ -162,7 +162,7 @@ export default function ScheduledActions() {
<select
value={newAction.contractAddress}
onChange={(e) => setNewAction({ ...newAction, contractAddress: e.target.value as `0x${string}` })}
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:border-blue-500"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:border-green-500"
>
<option value={CONTRACT_ADDRESSES.mainnet.MAINNET_TETHER}>MainnetTether</option>
<option value={CONTRACT_ADDRESSES.mainnet.TRANSACTION_MIRROR}>TransactionMirror</option>
@@ -176,7 +176,7 @@ export default function ScheduledActions() {
value={newAction.functionName}
onChange={(e) => setNewAction({ ...newAction, functionName: e.target.value })}
placeholder="pause"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500"
/>
</div>
@@ -185,7 +185,7 @@ export default function ScheduledActions() {
<select
value={newAction.schedule}
onChange={(e) => setNewAction({ ...newAction, schedule: e.target.value })}
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:border-blue-500"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:border-green-500"
>
<option value="daily">Daily</option>
<option value="weekly">Weekly</option>

View File

@@ -107,7 +107,7 @@ export default function TimeLockedActions() {
value={delayMinutes}
onChange={(e) => setDelayMinutes(parseInt(e.target.value) || 0)}
min="0"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:border-blue-500"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:border-green-500"
/>
</div>

View File

@@ -231,7 +231,7 @@ export default function TransactionMirrorAdmin() {
value={newAdmin}
onChange={(e) => setNewAdmin(e.target.value)}
placeholder="0x..."
className="flex-1 px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500"
className="flex-1 px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500"
/>
<button
onClick={handleSetAdmin}
@@ -254,7 +254,7 @@ export default function TransactionMirrorAdmin() {
value={txHash}
onChange={(e) => setTxHash(e.target.value)}
placeholder="Transaction hash (0x...)"
className="flex-1 px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500 font-mono text-sm"
className="flex-1 px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500 font-mono text-sm"
/>
<button
onClick={handleCheckTransaction}

View File

@@ -89,7 +89,7 @@ export default function TransactionPreview() {
<select
value={contractAddress}
onChange={(e) => setContractAddress(e.target.value as `0x${string}`)}
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:border-blue-500"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:border-green-500"
>
<option value={CONTRACT_ADDRESSES.mainnet.MAINNET_TETHER}>MainnetTether</option>
<option value={CONTRACT_ADDRESSES.mainnet.TRANSACTION_MIRROR}>TransactionMirror</option>
@@ -103,7 +103,7 @@ export default function TransactionPreview() {
value={functionName}
onChange={(e) => setFunctionName(e.target.value)}
placeholder="pause"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500"
/>
</div>
@@ -114,7 +114,7 @@ export default function TransactionPreview() {
value={args}
onChange={(e) => setArgs(e.target.value)}
placeholder='[] or ["0x..."]'
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500 font-mono text-sm"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500 font-mono text-sm"
/>
</div>

View File

@@ -183,7 +183,7 @@ export default function TransactionTemplates() {
value={customTemplate.name}
onChange={(e) => setCustomTemplate({ ...customTemplate, name: e.target.value })}
placeholder="My Custom Template"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500"
/>
</div>
<div>
@@ -193,7 +193,7 @@ export default function TransactionTemplates() {
value={customTemplate.description}
onChange={(e) => setCustomTemplate({ ...customTemplate, description: e.target.value })}
placeholder="What this template does"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500"
/>
</div>
<div>
@@ -203,7 +203,7 @@ export default function TransactionTemplates() {
value={customTemplate.contractAddress}
onChange={(e) => setCustomTemplate({ ...customTemplate, contractAddress: e.target.value })}
placeholder="0x..."
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500 font-mono text-sm"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500 font-mono text-sm"
/>
</div>
<div>
@@ -213,7 +213,7 @@ export default function TransactionTemplates() {
value={customTemplate.functionName}
onChange={(e) => setCustomTemplate({ ...customTemplate, functionName: e.target.value })}
placeholder="pause"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500"
/>
</div>
<div>
@@ -223,7 +223,7 @@ export default function TransactionTemplates() {
value={customTemplate.args}
onChange={(e) => setCustomTemplate({ ...customTemplate, args: e.target.value })}
placeholder='[] or ["0x..."]'
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500 font-mono text-sm"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500 font-mono text-sm"
/>
</div>
<div>
@@ -231,7 +231,7 @@ export default function TransactionTemplates() {
<select
value={customTemplate.category}
onChange={(e) => setCustomTemplate({ ...customTemplate, category: e.target.value as any })}
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:border-blue-500"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:border-green-500"
>
<option value="maintenance">Maintenance</option>
<option value="emergency">Emergency</option>

View File

@@ -200,7 +200,7 @@ export default function TwoWayBridgeAdmin() {
value={newAdmin}
onChange={(e) => setNewAdmin(e.target.value)}
placeholder="0x..."
className="flex-1 px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500"
className="flex-1 px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500"
/>
<button
onClick={handleSetAdmin}
@@ -220,14 +220,14 @@ export default function TwoWayBridgeAdmin() {
value={chainSelector}
onChange={(e) => setChainSelector(e.target.value)}
placeholder="Chain Selector (e.g., 16015286601757825753)"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500"
/>
<input
type="text"
value={l2Bridge}
onChange={(e) => setL2Bridge(e.target.value)}
placeholder="L2 Bridge Address (0x...)"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500"
/>
<button
onClick={handleAddDestination}
@@ -247,7 +247,7 @@ export default function TwoWayBridgeAdmin() {
value={feeToken}
onChange={(e) => setFeeToken(e.target.value)}
placeholder="0x..."
className="flex-1 px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500"
className="flex-1 px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500"
/>
<button
onClick={handleUpdateFeeToken}

View File

@@ -96,7 +96,7 @@ export default function WalletBackup() {
value={backupPassword}
onChange={(e) => setBackupPassword(e.target.value)}
placeholder="Enter password"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500"
/>
</div>
<button
@@ -119,7 +119,7 @@ export default function WalletBackup() {
onChange={(e) => setImportData(e.target.value)}
placeholder="Paste encrypted backup data..."
rows={6}
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500 font-mono text-sm"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500 font-mono text-sm"
/>
</div>
<div>
@@ -129,7 +129,7 @@ export default function WalletBackup() {
value={backupPassword}
onChange={(e) => setBackupPassword(e.target.value)}
placeholder="Enter password"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500"
/>
</div>
<button

View File

@@ -57,7 +57,7 @@ export default function WalletBalance() {
value={newWallet}
onChange={(e) => setNewWallet(e.target.value)}
placeholder="0x..."
className="flex-1 px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500 font-mono text-sm"
className="flex-1 px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500 font-mono text-sm"
/>
<button
onClick={addWallet}

View File

@@ -103,7 +103,7 @@ export default function WalletDeployment() {
value={config.name}
onChange={(e) => setConfig({ ...config, name: e.target.value })}
placeholder="My Admin Wallet"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500"
/>
</div>
@@ -132,7 +132,7 @@ export default function WalletDeployment() {
value={newOwner}
onChange={(e) => setNewOwner(e.target.value)}
placeholder="0x..."
className="flex-1 px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500 font-mono text-sm"
className="flex-1 px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500 font-mono text-sm"
/>
<button
onClick={addOwner}
@@ -159,7 +159,7 @@ export default function WalletDeployment() {
}
min="1"
max={config.owners.length}
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:border-blue-500"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:border-green-500"
/>
<p className="text-white/60 text-xs mt-1">
Requires {config.threshold} of {config.owners.length} owners to approve transactions

View File

@@ -144,7 +144,7 @@ export default function WalletDeploymentEnhanced() {
value={config.name}
onChange={(e) => setConfig({ ...config, name: e.target.value })}
placeholder="My Admin Wallet"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500"
/>
</div>
@@ -173,7 +173,7 @@ export default function WalletDeploymentEnhanced() {
value={newOwner}
onChange={(e) => setNewOwner(e.target.value)}
placeholder="0x..."
className="flex-1 px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-blue-500 font-mono text-sm"
className="flex-1 px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-green-500 font-mono text-sm"
/>
<button
onClick={addOwner}
@@ -200,7 +200,7 @@ export default function WalletDeploymentEnhanced() {
}
min="1"
max={config.owners.length}
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:border-blue-500"
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:border-green-500"
/>
<p className="text-white/60 text-xs mt-1">
Requires {config.threshold} of {config.owners.length} owners to approve transactions

View File

@@ -6,9 +6,9 @@
import { useState, useEffect } from 'react';
import toast from 'react-hot-toast';
import { useAccount } from 'wagmi';
import DashboardSection from '../../features/omnl-dashboard/DashboardSection';
import NasaIcon, { StatusIcon } from '../icons/NasaIcon';
import NasaPageHeader from '../../features/omnl-dashboard/NasaPageHeader';
import DashboardSection from '../../../features/omnl-dashboard/DashboardSection';
import NasaIcon, { StatusIcon } from '../../icons/NasaIcon';
import NasaPageHeader from '../../../features/omnl-dashboard/NasaPageHeader';
interface ChainMetadata {
chainId: number;
@@ -80,8 +80,8 @@ export default function ChainManagementDashboard() {
if (!isConnected) {
return (
<div className="dash-card nasa-mission-root">
<p className="text-[#7a9bb8] flex items-center gap-2">
<div className="dash-card app-shell text-center">
<p className="text-[#848e9c] flex items-center justify-center gap-2">
<NasaIcon name="wallet" size={16} className="nasa-icon--telemetry" />
Please connect your wallet to manage chains.
</p>
@@ -90,10 +90,9 @@ export default function ChainManagementDashboard() {
}
return (
<div className="space-y-6 nasa-mission-root">
<div className="space-y-6 app-page-shell app-shell">
<NasaPageHeader
icon="network"
missionId="NET-ADM"
title="Chain Management"
subtitle="Supported chains, adapters, and deployment registry"
/>
@@ -116,7 +115,7 @@ export default function ChainManagementDashboard() {
<p className="text-[#7a9bb8] text-sm font-mono">Adapter: {chain.adapter.slice(0, 10)}</p>
<p className="text-[#7a9bb8] text-sm">
Explorer:{' '}
<a href={chain.explorerUrl} target="_blank" rel="noopener noreferrer" className="text-[#00d4ff] hover:underline inline-flex items-center gap-1">
<a href={chain.explorerUrl} target="_blank" rel="noopener noreferrer" className="text-[#4ade80] hover:underline inline-flex items-center gap-1">
<NasaIcon name="link" size={12} />
{chain.explorerUrl}
</a>

View File

@@ -77,7 +77,7 @@ export default function SwapBridgeSwapQuoteForm() {
placeholder="Source token address"
value={sourceToken}
onChange={(e) => setSourceToken(e.target.value)}
className="px-3 py-2 rounded-lg bg-[#252830] text-white border border-white/20 focus:ring-2 focus:ring-teal-500/50 placeholder:text-[#A0A0A0]"
className="px-3 py-2 rounded-lg bg-[#252830] text-white border border-white/20 focus:ring-2 focus:ring-green-500/50 placeholder:text-[#A0A0A0]"
/>
<label htmlFor="quote-destination-token" className="sr-only">Destination token address</label>
<input
@@ -87,7 +87,7 @@ export default function SwapBridgeSwapQuoteForm() {
placeholder="Destination token address"
value={destinationToken}
onChange={(e) => setDestinationToken(e.target.value)}
className="px-3 py-2 rounded-lg bg-[#252830] text-white border border-white/20 focus:ring-2 focus:ring-teal-500/50 placeholder:text-[#A0A0A0]"
className="px-3 py-2 rounded-lg bg-[#252830] text-white border border-white/20 focus:ring-2 focus:ring-green-500/50 placeholder:text-[#A0A0A0]"
/>
<label htmlFor="quote-source-chain" className="sr-only">Source chain ID</label>
<input
@@ -97,7 +97,7 @@ export default function SwapBridgeSwapQuoteForm() {
placeholder="Source chain ID"
value={sourceChainId}
onChange={(e) => setSourceChainId(e.target.value)}
className="px-3 py-2 rounded-lg bg-[#252830] text-white border border-white/20 focus:ring-2 focus:ring-teal-500/50 placeholder:text-[#A0A0A0]"
className="px-3 py-2 rounded-lg bg-[#252830] text-white border border-white/20 focus:ring-2 focus:ring-green-500/50 placeholder:text-[#A0A0A0]"
/>
<label htmlFor="quote-destination-chain" className="sr-only">Destination chain ID</label>
<input
@@ -107,7 +107,7 @@ export default function SwapBridgeSwapQuoteForm() {
placeholder="Destination chain ID"
value={destinationChainId}
onChange={(e) => setDestinationChainId(e.target.value)}
className="px-3 py-2 rounded-lg bg-[#252830] text-white border border-white/20 focus:ring-2 focus:ring-teal-500/50 placeholder:text-[#A0A0A0]"
className="px-3 py-2 rounded-lg bg-[#252830] text-white border border-white/20 focus:ring-2 focus:ring-green-500/50 placeholder:text-[#A0A0A0]"
/>
<label htmlFor="quote-amount" className="sr-only">Amount (wei or decimal)</label>
<input
@@ -117,14 +117,14 @@ export default function SwapBridgeSwapQuoteForm() {
placeholder="Amount (wei or decimal)"
value={amount}
onChange={(e) => setAmount(e.target.value)}
className="px-3 py-2 rounded-lg bg-[#252830] text-white border border-white/20 focus:ring-2 focus:ring-teal-500/50 placeholder:text-[#A0A0A0] md:col-span-2"
className="px-3 py-2 rounded-lg bg-[#252830] text-white border border-white/20 focus:ring-2 focus:ring-green-500/50 placeholder:text-[#A0A0A0] md:col-span-2"
/>
</div>
<button
type="button"
onClick={handleGetQuote}
disabled={loading}
className="px-4 py-2 rounded-lg bg-teal-600 text-white font-medium hover:bg-teal-500 disabled:opacity-50"
className="px-4 py-2 rounded-lg bg-green-600 text-white font-medium hover:bg-green-500 disabled:opacity-50"
>
{loading ? 'Getting quote...' : 'Get full path quote'}
</button>

View File

@@ -23,7 +23,12 @@ export type NasaIconName =
| 'globe'
| 'money'
| 'rail'
| 'admin';
| 'admin'
| 'dashboard'
| 'code'
| 'shield'
| 'terminal'
| 'plus';
type Props = SVGProps<SVGSVGElement> & {
name: NasaIconName;
@@ -170,6 +175,39 @@ const paths: Record<NasaIconName, ReactNode> = {
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1Z" />
</>
),
dashboard: (
<>
<rect x="3" y="3" width="7" height="9" rx="1" />
<rect x="14" y="3" width="7" height="5" rx="1" />
<rect x="14" y="12" width="7" height="9" rx="1" />
<rect x="3" y="16" width="7" height="5" rx="1" />
</>
),
code: (
<>
<path d="m16 18 6-6-6-6" />
<path d="m8 6-6 6 6 6" />
</>
),
shield: (
<>
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
<path d="m9 12 2 2 4-4" />
</>
),
terminal: (
<>
<rect x="2" y="4" width="20" height="16" rx="2" />
<path d="m6 10 4 4-4 4" />
<path d="M12 18h6" />
</>
),
plus: (
<>
<path d="M12 5v14" />
<path d="M5 12h14" />
</>
),
};
export default function NasaIcon({ name, size = 18, className = '', ...props }: Props) {

View File

@@ -1,86 +1,92 @@
import { Link, Outlet, useLocation } from 'react-router-dom'
import { useRef } from 'react'
import { useRef, useState } from 'react'
import WalletConnect from '../wallet/WalletConnect'
import WalletDisconnectNotice from '../wallet/WalletDisconnectNotice'
import { defaultFrontendExplorerUrl } from '../../config/networks'
import MobileNavDrawer from './MobileNavDrawer'
import MobileBottomNav from './MobileBottomNav'
export default function Layout() {
const location = useLocation()
const userInitiatedDisconnectRef = useRef(false)
const [menuOpen, setMenuOpen] = useState(false)
const isActive = (path: string) => location.pathname === path
const navItems = [
{ to: '/hub', label: 'DBIS Dashboard', active: isActive('/hub') },
{ to: '/bridge', label: 'Bridge', active: isActive('/bridge') },
{ to: '/swap', label: 'Swap', active: isActive('/swap') },
{ to: '/reserve', label: 'Reserve', active: isActive('/reserve') },
{ to: '/history', label: 'History', active: isActive('/history') },
{ to: '/wallets', label: 'Wallets', active: isActive('/wallets') },
{ to: '/admin', label: 'Admin', active: isActive('/admin') },
{ to: '/docs', label: 'Developers', active: isActive('/docs') },
]
return (
<div className="min-h-screen relative bg-[#1a1d24] flex flex-col">
<div className="min-h-screen relative bg-[#141a16] flex flex-col dbis-has-bottom-nav">
<WalletDisconnectNotice userInitiatedDisconnectRef={userInitiatedDisconnectRef} />
<nav className="bg-[#252830] border-b border-white/10 sticky top-0 z-50">
<div className="container mx-auto px-4 max-w-7xl">
<div className="flex justify-between items-center h-14 gap-4">
<div className="flex items-center gap-6 min-w-0">
<Link to="/" className="text-xl font-semibold text-white whitespace-nowrap flex items-center gap-2">
<span className="text-teal-400 font-bold">DBIS</span>
<nav className="mobile-nav-bar bg-[#1a241c] border-b border-green-500/20 sticky top-0 z-50 safe-area-top">
<div className="container mx-auto px-3 sm:px-4 max-w-7xl">
<div className="flex justify-between items-center h-14 gap-2">
<div className="flex items-center gap-2 min-w-0 flex-1">
<button
type="button"
className="sm:hidden shrink-0 text-white p-2 min-h-[44px] min-w-[44px] rounded-lg hover:bg-white/10"
aria-label="Open menu"
aria-expanded={menuOpen}
onClick={() => setMenuOpen(true)}
>
</button>
<Link to="/hub" className="text-lg sm:text-xl font-semibold text-white whitespace-nowrap flex items-center gap-2">
<span className="text-green-400 font-bold">DBIS</span>
</Link>
<div className="hidden sm:flex gap-1">
<Link
to="/bridge"
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
isActive('/bridge') || isActive('/') ? 'bg-teal-600 text-white' : 'text-[#A0A0A0] hover:text-white hover:bg-white/5'
}`}
>
Bridge
</Link>
<Link
to="/swap"
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
isActive('/swap') ? 'bg-teal-600 text-white' : 'text-[#A0A0A0] hover:text-white hover:bg-white/5'
}`}
>
Swap
</Link>
<Link
to="/history"
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
isActive('/history') ? 'bg-teal-600 text-white' : 'text-[#A0A0A0] hover:text-white hover:bg-white/5'
}`}
>
History
</Link>
<Link
to="/wallets"
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
isActive('/wallets') ? 'bg-teal-600 text-white' : 'text-[#A0A0A0] hover:text-white hover:bg-white/5'
}`}
>
Wallets
</Link>
<div className="hidden sm:flex gap-1 ml-2 overflow-x-auto">
{navItems.filter((i) => i.to && !['/central-bank', '/office-24', '/trade', '/docs', '/admin', '/hub'].includes(i.to)).map((item) => (
<Link
key={item.to}
to={item.to!}
className={`px-3 py-2 rounded-lg text-sm font-medium transition-colors whitespace-nowrap min-h-[44px] flex items-center ${
item.active ? 'bg-green-600 text-white' : 'text-[#A0A0A0] hover:text-white hover:bg-white/5'
}`}
>
{item.label}
</Link>
))}
<a
href={defaultFrontendExplorerUrl}
target="_blank"
rel="noopener noreferrer"
className="px-4 py-2 rounded-lg text-sm font-medium text-[#A0A0A0] hover:text-white hover:bg-white/5 transition-colors"
className="px-3 py-2 rounded-lg text-sm font-medium text-[#A0A0A0] hover:text-white hover:bg-white/5 transition-colors whitespace-nowrap min-h-[44px] flex items-center"
>
Explorer
</a>
</div>
</div>
<div className="flex-shrink-0 w-full sm:w-auto max-w-[200px] sm:max-w-none">
<div className="flex-shrink-0 max-w-[50%] sm:max-w-none">
<WalletConnect onBeforeDisconnect={() => { userInitiatedDisconnectRef.current = true }} />
</div>
</div>
</div>
</nav>
<main className="flex-1">
<MobileNavDrawer open={menuOpen} onClose={() => setMenuOpen(false)} title="DBIS Navigation" items={navItems} />
<MobileBottomNav onOpenMenu={() => setMenuOpen(true)} />
<main className="flex-1 w-full min-w-0 overflow-x-hidden">
<Outlet />
</main>
<footer className="border-t border-white/10 py-6 mt-auto">
<footer className="border-t border-white/10 py-6 mt-auto safe-area-bottom">
<div className="container mx-auto px-4 max-w-7xl">
<div className="flex flex-wrap justify-center items-center gap-x-6 gap-y-2 text-sm text-[#A0A0A0]">
<div className="flex flex-wrap justify-center items-center gap-x-4 gap-y-2 text-xs sm:text-sm text-[#A0A0A0]">
<span> Contracts verified</span>
<span> Powered by CCIP</span>
<span> Secure cross-chain messaging</span>
<span> Trackable on Explorer</span>
<Link to="/wallets" className="hover:text-teal-400 transition-colors">Wallets</Link>
<Link to="/docs" className="hover:text-teal-400 transition-colors">Developers</Link>
<span className="hidden sm:inline"> Secure cross-chain messaging</span>
<Link to="/hub" className="hover:text-green-400 transition-colors min-h-[44px] flex items-center">DBIS Dashboard</Link>
<Link to="/wallets" className="hover:text-green-400 transition-colors min-h-[44px] flex items-center">Wallets</Link>
<Link to="/docs" className="hover:text-green-400 transition-colors min-h-[44px] flex items-center">Developers</Link>
</div>
</div>
</footer>

View File

@@ -0,0 +1,43 @@
import { Link, useLocation } from 'react-router-dom';
import NasaIcon from '../icons/NasaIcon';
type Props = {
onOpenMenu: () => void;
};
const TABS = [
{ to: '/hub', label: 'Home', icon: 'dashboard' as const },
{ to: '/bridge', label: 'Bridge', icon: 'bridge' as const },
{ to: '/trade', label: 'Trade', icon: 'trade' as const },
{ to: '/central-bank', label: 'Z Bank', icon: 'central-bank' as const },
];
export default function MobileBottomNav({ onOpenMenu }: Props) {
const location = useLocation();
const isActive = (path: string) => {
if (path === '/hub') return location.pathname === '/hub' || location.pathname === '/';
if (path === '/bridge') return location.pathname === '/bridge' || location.pathname.startsWith('/bridge');
if (path === '/') return location.pathname === '/';
return location.pathname.startsWith(path);
};
return (
<nav className="dbis-bottom-nav md:hidden" aria-label="Primary navigation">
{TABS.map(({ to, label, icon }) => (
<Link
key={to}
to={to}
className={`dbis-bottom-nav__item${isActive(to) ? ' dbis-bottom-nav__item--active' : ''}`}
>
<NasaIcon name={icon} size={20} />
<span>{label}</span>
</Link>
))}
<button type="button" className="dbis-bottom-nav__item" onClick={onOpenMenu} aria-label="Open menu">
<span className="text-lg leading-none"></span>
<span>More</span>
</button>
</nav>
);
}

View File

@@ -0,0 +1,80 @@
import { useEffect } from 'react';
import { Link } from 'react-router-dom';
export type MobileNavItem = {
to?: string;
href?: string;
label: string;
active?: boolean;
onClick?: () => void;
};
type Props = {
open: boolean;
onClose: () => void;
title: string;
items: MobileNavItem[];
};
export default function MobileNavDrawer({ open, onClose, title, items }: Props) {
useEffect(() => {
if (!open) return;
const prev = document.body.style.overflow;
document.body.style.overflow = 'hidden';
return () => {
document.body.style.overflow = prev;
};
}, [open]);
if (!open) return null;
return (
<div className="fixed inset-0 z-[100] lg:hidden" role="dialog" aria-modal="true" aria-label={title}>
<button
type="button"
className="absolute inset-0 bg-black/75 backdrop-blur-sm"
aria-label="Close menu"
onClick={onClose}
/>
<nav className="absolute top-0 left-0 bottom-0 w-[min(100vw,20rem)] bg-[#141a16] border-r border-green-500/20 flex flex-col shadow-2xl safe-area-pad">
<div className="flex items-center justify-between px-4 py-3 border-b border-white/10 min-h-[56px]">
<span className="text-white font-semibold text-sm">{title}</span>
<button
type="button"
onClick={onClose}
className="text-white/80 hover:text-white p-2 min-h-[44px] min-w-[44px] flex items-center justify-center rounded-lg"
aria-label="Close"
>
</button>
</div>
<ul className="flex-1 overflow-y-auto py-2">
{items.map((item) => {
const className = `block w-full text-left px-4 py-3 text-sm font-medium min-h-[44px] ${
item.active ? 'bg-green-600/20 text-green-300 border-l-2 border-green-400' : 'text-[#A0A0A0] hover:text-white hover:bg-white/5'
}`;
if (item.href) {
return (
<li key={item.href}>
<a href={item.href} target="_blank" rel="noopener noreferrer" className={className} onClick={onClose}>
{item.label}
</a>
</li>
);
}
if (item.to) {
return (
<li key={item.to}>
<Link to={item.to} className={className} onClick={() => { item.onClick?.(); onClose(); }}>
{item.label}
</Link>
</li>
);
}
return null;
})}
</ul>
</nav>
</div>
);
}

View File

@@ -1,122 +1,92 @@
import { Link, Outlet, useLocation } from 'react-router-dom';
import { useRef } from 'react';
import { useRef, useState } from 'react';
import WalletConnect from '../wallet/WalletConnect';
import WalletDisconnectNotice from '../wallet/WalletDisconnectNotice';
import NasaIcon, { type NasaIconName } from '../icons/NasaIcon';
import MobileNavDrawer from './MobileNavDrawer';
import MobileBottomNav from './MobileBottomNav';
const PRODUCTS: { path: string; label: string; icon: NasaIconName }[] = [
{ path: '/hub', label: 'Hub', icon: 'satellite' },
{ path: '/hub', label: 'DBIS Dashboard', icon: 'dashboard' },
{ path: '/central-bank', label: 'Central Bank', icon: 'central-bank' },
{ path: '/office-24', label: 'Office 24', icon: 'office' },
{ path: '/trade', label: 'DBIS Trade', icon: 'trade' },
{ path: '/swap', label: 'Digital Swap', icon: 'swap' },
{ path: '/office-24', label: 'O24 Central Bank', icon: 'office' },
{ path: '/trade', label: 'Trade', icon: 'trade' },
{ path: '/swap', label: 'Swap', icon: 'swap' },
];
export default function OmnlProductLayout() {
const location = useLocation();
const userInitiatedDisconnectRef = useRef(false);
const [menuOpen, setMenuOpen] = useState(false);
const isActive = (path: string) =>
path === '/hub' ? location.pathname === '/hub' : location.pathname.startsWith(path);
const drawerItems = [
...PRODUCTS.map((p) => ({ to: p.path, label: p.label, active: isActive(p.path) })),
{ to: '/bridge', label: 'Bridge', active: location.pathname === '/bridge' },
{ to: '/admin', label: 'Admin', active: location.pathname === '/admin' },
{ to: '/wallets', label: 'Wallets', active: location.pathname === '/wallets' },
];
const isActive = (path: string) => location.pathname.startsWith(path);
const isBanking = location.pathname === '/central-bank';
return (
<div className="omnl-app min-h-screen flex flex-col">
<div className={`omnl-app min-h-screen flex flex-col dbis-has-bottom-nav${isBanking ? ' omnl-banking-theme' : ''}`}>
<WalletDisconnectNotice userInitiatedDisconnectRef={userInitiatedDisconnectRef} />
<header className="omnl-app-header sticky top-0 z-50 border-b">
<div className="max-w-[1600px] mx-auto px-4 h-14 flex items-center gap-4">
<Link to="/hub" className="flex items-center gap-2 shrink-0">
<span className="w-8 h-8 border border-[#2d6bb8] bg-[#163a6b] text-[#00d4ff] font-bold flex items-center justify-center text-sm">
<NasaIcon name="satellite" size={16} className="nasa-icon--telemetry" />
<header className="omnl-app-header mobile-nav-bar sticky top-0 z-50 border-b border-green-500/20 safe-area-top bg-[#1a241c]">
<div className="max-w-[1600px] mx-auto px-3 sm:px-4 h-14 flex items-center gap-2 sm:gap-4">
<button
type="button"
className="md:hidden shrink-0 text-white p-2 min-h-[44px] min-w-[44px] rounded-lg hover:bg-white/10"
aria-label="Open menu"
aria-expanded={menuOpen}
onClick={() => setMenuOpen(true)}
>
</button>
<Link to="/hub" className="flex items-center gap-2 shrink-0 min-w-0">
<span className="w-8 h-8 rounded-lg bg-green-600 text-white font-bold flex items-center justify-center text-sm shrink-0">
<NasaIcon name="central-bank" size={16} />
</span>
<span className="nasa-nav-brand font-semibold hidden sm:inline text-sm">OMNL Mission Control</span>
<span className="font-semibold hidden sm:inline text-sm truncate text-white">DBIS OMNL</span>
</Link>
<nav className="flex items-center gap-1 flex-1 overflow-x-auto" aria-label="OMNL products">
<nav className="hidden md:flex items-center gap-1 flex-1 overflow-x-auto min-w-0" aria-label="OMNL products">
{PRODUCTS.map(({ path, label, icon }) => (
<Link
key={path}
to={path}
className={`nasa-nav-link shrink-0 ${isActive(path) ? 'nasa-nav-link--active' : ''}`}
className={`px-3 py-2 rounded-lg text-sm font-medium transition-colors whitespace-nowrap min-h-[40px] flex items-center gap-1.5 ${
isActive(path) ? 'bg-green-600 text-white' : 'text-[#848e9c] hover:text-white hover:bg-white/5'
}`}
>
<NasaIcon name={icon} size={14} />
{label}
<span className="hidden lg:inline">{label}</span>
<span className="lg:hidden">{label.split(' ')[0]}</span>
</Link>
))}
</nav>
<Link
to="/bridge"
className="nasa-nav-link hidden md:inline shrink-0 !border-transparent"
className="hidden lg:inline-flex items-center gap-1.5 px-3 py-2 rounded-lg text-sm font-medium text-[#848e9c] hover:text-white hover:bg-white/5 min-h-[40px]"
>
<NasaIcon name="bridge" size={14} />
Bridge
</Link>
<WalletConnect onBeforeDisconnect={() => { userInitiatedDisconnectRef.current = true; }} />
<div className="shrink-0 max-w-[45%] sm:max-w-none ml-auto">
<WalletConnect onBeforeDisconnect={() => { userInitiatedDisconnectRef.current = true; }} />
</div>
</div>
</header>
<main className="flex-1">
<MobileNavDrawer open={menuOpen} onClose={() => setMenuOpen(false)} title="DBIS OMNL" items={drawerItems} />
<MobileBottomNav onOpenMenu={() => setMenuOpen(true)} />
<main className="flex-1 w-full min-w-0 overflow-x-hidden">
<Outlet />
</main>
</div>
);
}

View File

@@ -0,0 +1,41 @@
import { Link, useLocation } from 'react-router-dom';
import NasaIcon from '../icons/NasaIcon';
type Props = {
onOpenMenu: () => void;
};
const TABS = [
{ to: '/z', label: 'Hub', icon: 'dashboard' as const },
{ to: '/z-wallet', label: 'Wallet', icon: 'wallet' as const },
{ to: '/swap', label: 'Swap', icon: 'swap' as const },
{ to: '/central-bank', label: 'Z Bank', icon: 'central-bank' as const },
];
export default function ZMobileBottomNav({ onOpenMenu }: Props) {
const location = useLocation();
const isActive = (path: string) => {
if (path === '/z') return location.pathname === '/z' || location.pathname === '/';
return location.pathname.startsWith(path);
};
return (
<nav className="z-bottom-nav md:hidden" aria-label="Z Ecosystem navigation">
{TABS.map(({ to, label, icon }) => (
<Link
key={to}
to={to}
className={`z-bottom-nav__item${isActive(to) ? ' z-bottom-nav__item--active' : ''}`}
>
<NasaIcon name={icon} size={20} />
<span>{label}</span>
</Link>
))}
<button type="button" className="z-bottom-nav__item" onClick={onOpenMenu} aria-label="Open menu">
<span className="text-lg leading-none"></span>
<span>More</span>
</button>
</nav>
);
}

View File

@@ -0,0 +1,84 @@
import { Link, Outlet, useLocation } from 'react-router-dom';
import { useRef, useState } from 'react';
import WalletConnect from '../wallet/WalletConnect';
import WalletDisconnectNotice from '../wallet/WalletDisconnectNotice';
import NasaIcon, { type NasaIconName } from '../icons/NasaIcon';
import MobileNavDrawer from './MobileNavDrawer';
import ZMobileBottomNav from './ZMobileBottomNav';
import { Z_ECOSYSTEM_PUBLIC_URL } from '../../config/zEcosystemDomain';
const PRODUCTS: { path: string; label: string; icon: NasaIconName }[] = [
{ path: '/z', label: 'Z Hub', icon: 'dashboard' },
{ path: '/z-wallet', label: 'Z Wallet', icon: 'wallet' },
{ path: '/swap', label: 'Swap', icon: 'swap' },
{ path: '/central-bank', label: 'Z Online Bank', icon: 'central-bank' },
];
export default function ZProductLayout() {
const location = useLocation();
const userInitiatedDisconnectRef = useRef(false);
const [menuOpen, setMenuOpen] = useState(false);
const isActive = (path: string) =>
path === '/z' ? location.pathname === '/z' : location.pathname.startsWith(path);
const drawerItems = PRODUCTS.map((p) => ({ to: p.path, label: p.label, active: isActive(p.path) }));
const isBanking = location.pathname === '/central-bank';
return (
<div
className={`omnl-app min-h-screen flex flex-col dbis-has-bottom-nav z-ecosystem-theme${isBanking ? ' omnl-banking-theme' : ''}`}
>
<WalletDisconnectNotice userInitiatedDisconnectRef={userInitiatedDisconnectRef} />
<header className="omnl-app-header mobile-nav-bar sticky top-0 z-50 border-b border-purple-500/30 safe-area-top bg-[#1a1624]">
<div className="max-w-[1600px] mx-auto px-3 sm:px-4 h-14 flex items-center gap-2 sm:gap-4">
<button
type="button"
className="md:hidden shrink-0 text-white p-2 min-h-[44px] min-w-[44px] rounded-lg hover:bg-white/10"
aria-label="Open menu"
aria-expanded={menuOpen}
onClick={() => setMenuOpen(true)}
>
</button>
<Link to="/z" className="flex items-center gap-2 shrink-0 min-w-0">
<span className="w-8 h-8 rounded-lg bg-purple-600 text-white font-bold flex items-center justify-center text-sm shrink-0">
Z
</span>
<span className="font-semibold hidden sm:inline text-sm truncate text-white">Z Blockchain System</span>
</Link>
<nav className="hidden md:flex items-center gap-1 flex-1 overflow-x-auto min-w-0" aria-label="Z Ecosystem">
{PRODUCTS.map(({ path, label, icon }) => (
<Link
key={path}
to={path}
className={`px-3 py-2 rounded-lg text-sm font-medium transition-colors whitespace-nowrap min-h-[40px] flex items-center gap-1.5 ${
isActive(path) ? 'bg-purple-600 text-white' : 'text-[#848e9c] hover:text-white hover:bg-white/5'
}`}
>
<NasaIcon name={icon} size={14} />
{label}
</Link>
))}
</nav>
<div className="shrink-0 max-w-[45%] sm:max-w-none ml-auto">
<WalletConnect onBeforeDisconnect={() => { userInitiatedDisconnectRef.current = true; }} />
</div>
</div>
</header>
<MobileNavDrawer open={menuOpen} onClose={() => setMenuOpen(false)} title="Z Ecosystem" items={drawerItems} />
<ZMobileBottomNav onOpenMenu={() => setMenuOpen(true)} />
<main className="flex-1">
<Outlet />
</main>
<footer className="text-center text-xs text-[#6b7280] py-3 border-t border-white/5">
{Z_ECOSYSTEM_PUBLIC_URL.replace(/^https?:\/\//, '')}
</footer>
</div>
);
}

View File

@@ -34,7 +34,7 @@ export default function ToastProvider() {
},
loading: {
iconTheme: {
primary: '#3b82f6',
primary: '#22c55e',
secondary: '#fff',
},
},

View File

@@ -128,7 +128,7 @@ export default function WalletConnect({ onBeforeDisconnect }: WalletConnectProps
<>
<button
onClick={() => setShowConnectModal(true)}
className="px-6 py-3 bg-teal-600 text-white font-medium rounded-xl hover:bg-teal-500 transition-colors focus:outline-none focus:ring-2 focus:ring-teal-500 focus:ring-offset-2 focus:ring-offset-[#252830] min-h-[44px] w-full md:w-auto"
className="px-6 py-3 bg-green-600 text-white font-medium rounded-xl hover:bg-green-500 transition-colors focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 focus:ring-offset-[#252830] min-h-[44px] w-full md:w-auto"
>
Connect Wallet
</button>
@@ -152,7 +152,7 @@ export default function WalletConnect({ onBeforeDisconnect }: WalletConnectProps
>
<span>{connector.name}</span>
{isPending && (
<svg className="animate-spin h-5 w-5 text-teal-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<svg className="animate-spin h-5 w-5 text-green-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
</svg>

View File

@@ -13,6 +13,8 @@ export const CHAIN_ICON_URLS: Record<number, string> = {
100: `${TW}/xdai/info/logo.png`,
137: `${TW}/polygon/info/logo.png`,
138: ETH_DIAMOND, // Chain 138 (DBIS) - from chain-138.json
900001: ETH_DIAMOND, // Shiva Blockchain (OneX)
900002: ETH_DIAMOND, // Z Chain
42161: `${TW}/arbitrum/info/logo.png`,
43114: `${TW}/avalanchec/info/logo.png`,
8453: `${TW}/base/info/logo.png`,

View File

@@ -9,28 +9,29 @@ function prodOrLocal(envValue: string | undefined, prodPath: string, localUrl: s
export const TOKEN_AGGREGATION_URL = prodOrLocal(
import.meta.env.VITE_TOKEN_AGGREGATION_URL,
'/api/v1',
'https://explorer.d-bis.org/api/v1',
'http://localhost:3000',
);
export const DBIS_EXCHANGE_URL = prodOrLocal(
import.meta.env.VITE_DBIS_EXCHANGE_URL,
'/exchange',
'https://exchange.omdnl.org/exchange',
'http://localhost:3012',
);
export const SETTLEMENT_MIDDLEWARE_URL = prodOrLocal(
import.meta.env.VITE_SETTLEMENT_MIDDLEWARE_URL,
'/settlement',
'https://secure.omdnl.org/settlement',
'http://localhost:3011',
);
/** Production nginx maps /settlement/* → settlement-middleware /api/v1/settlement/* */
/** Production nginx maps /settlement/* and /exchange/* to middleware; bare localhost needs /api/v1/... */
function proxiedServiceUrl(base: string, servicePrefix: string, path: string): string {
const root = base.replace(/\/$/, '');
const suffix = path.startsWith('/') ? path : `/${path}`;
if (root.startsWith('/')) {
return `${root}${suffix}`;
}
return `${root}${servicePrefix}${suffix}`;
const nginxPortal = /\/(settlement|exchange)$/.test(root);
return nginxPortal ? `${root}${suffix}` : `${root}${servicePrefix}${suffix}`;
}
export function settlementApi(path: string): string {
@@ -41,11 +42,18 @@ export function exchangeApi(path: string): string {
return proxiedServiceUrl(DBIS_EXCHANGE_URL, '/api/v1/exchange', path);
}
/** Bearer auth for OMNL mutating APIs — production portals inject via nginx; never embed operator keys in browser. */
/** Token aggregation OMNL routes (fiat LP positions, quotes). */
export function tokenAggregationApi(path: string): string {
const root = TOKEN_AGGREGATION_URL.replace(/\/$/, '');
const suffix = path.startsWith('/') ? path : `/${path}`;
if (root.endsWith('/api/v1')) return `${root}${suffix}`;
return `${root}/api/v1${suffix}`;
}
/** Bearer auth for OMNL mutating APIs — set VITE_OMNL_API_KEY at build for tunnel deploy; nginx injects in prod portals. */
export function omnlApiHeaders(json = true): Record<string, string> {
const headers: Record<string, string> = {};
if (json) headers['Content-Type'] = 'application/json';
if (import.meta.env.PROD) return headers;
const key = import.meta.env.VITE_OMNL_API_KEY?.trim();
if (key) headers.Authorization = `Bearer ${key}`;
return headers;

View File

@@ -0,0 +1,16 @@
import type { EcosystemLink } from './ecosystemLinks';
/** Paths served outside the React SPA (static HTML or proxied APIs). */
export function isStaticPortalPath(href: string): boolean {
return (
href.startsWith('/omnl') ||
href.startsWith('/api') ||
href.startsWith('/settlement') ||
href.startsWith('/exchange') ||
href.startsWith('/reserve/institutional')
);
}
export function ecosystemLinkUsesAnchor(link: EcosystemLink): boolean {
return Boolean(link.external) || link.href.startsWith('http') || isStaticPortalPath(link.href);
}

View File

@@ -0,0 +1,77 @@
import type { NasaIconName } from '../components/icons/NasaIcon';
import { defaultFrontendExplorerUrl } from './networks';
export type EcosystemLinkAccent = 'green' | 'teal' | 'blue' | 'gold' | 'purple' | 'slate';
export type EcosystemLink = {
id: string;
label: string;
description: string;
icon: NasaIconName;
href: string;
external?: boolean;
accent?: EcosystemLinkAccent;
};
/** In-app React routes (OMNL / DBIS — no Z products) */
export const IN_APP_LINKS: EcosystemLink[] = [
{ id: 'bridge', label: 'Bridge', description: 'Cross-chain bridge, CCIP, XRPL, trustless', icon: 'bridge', href: '/bridge', accent: 'teal' },
{ id: 'swap', label: 'Digital Swap', description: 'M2 load, token swap, settlement rails', icon: 'swap', href: '/swap', accent: 'blue' },
{ id: 'trade', label: 'DBIS Trade', description: 'Markets, order book, spot trading', icon: 'trade', href: '/trade', accent: 'gold' },
{ id: 'central-bank', label: 'Central Bank', description: 'OMNL balances & payments', icon: 'central-bank', href: '/central-bank', accent: 'green' },
{ id: 'office-24', label: 'Office 24 Central Bank', description: '128-bank corridors · IPSAS settlement', icon: 'office', href: '/office-24', accent: 'purple' },
{ id: 'reserve', label: 'Reserve', description: 'Coverage, attestation, peg status', icon: 'money', href: '/reserve', accent: 'green' },
{ id: 'history', label: 'History', description: 'Bridge transfer lookup & tracking', icon: 'ledger', href: '/history', accent: 'slate' },
{ id: 'wallets', label: 'Wallets', description: 'OneX Wallet · email, social, passkey on Shiva', icon: 'wallet', href: '/wallets', accent: 'blue' },
{ id: 'admin', label: 'Admin', description: 'Multisig, channels, bridge operators', icon: 'admin', href: '/admin', accent: 'slate' },
{ id: 'docs', label: 'Developers', description: 'API reference & integration', icon: 'code', href: '/docs', accent: 'teal' },
{ id: 'hub', label: 'DBIS Dashboard', description: 'All apps, consoles & API links', icon: 'dashboard', href: '/hub', accent: 'green' },
];
/** Primary apps shown in dashboard quick-access strip (OMNL only) */
export const FEATURED_APP_LINKS: EcosystemLink[] = [
IN_APP_LINKS.find((l) => l.id === 'central-bank')!,
IN_APP_LINKS.find((l) => l.id === 'bridge')!,
IN_APP_LINKS.find((l) => l.id === 'trade')!,
IN_APP_LINKS.find((l) => l.id === 'swap')!,
IN_APP_LINKS.find((l) => l.id === 'office-24')!,
];
/** Static operator consoles — served from /omnl/* (portal-serve or token-aggregation :3000) */
export const OPERATOR_CONSOLE_LINKS: EcosystemLink[] = [
{ id: 'compliance', label: 'OMNL Compliance Console', description: 'Posture, pending actions, Safe tx', icon: 'shield', href: '/omnl/compliance', accent: 'purple' },
{ id: 'omnl-dash', label: 'OMNL API Snapshot', description: 'Registry and Fineract compare JSON', icon: 'chart', href: '/omnl/dashboard', accent: 'blue' },
{ id: 'reserve-dash', label: 'Reserve Dashboard', description: 'Proof rows, coverage, institutional view', icon: 'money', href: '/reserve', accent: 'green' },
{ id: 'settlement-term', label: 'Settlement Terminal', description: 'Operator settlement terminal UI', icon: 'terminal', href: '/omnl/terminal', accent: 'gold' },
{ id: 'token-admin', label: 'Token Aggregation API', description: 'API catalog JSON', icon: 'ledger', href: '/api/v1/omnl/catalog', accent: 'teal' },
];
/** Public production portals (OMNL / DBIS) */
export const PUBLIC_PORTAL_LINKS: EcosystemLink[] = [
{ id: 'pub-cb', label: 'Central Bank', description: 'secure.omdnl.org', icon: 'central-bank', href: 'https://secure.omdnl.org/central-bank', external: true, accent: 'green' },
{ id: 'pub-online', label: 'Online banking', description: 'online.omdnl.org', icon: 'globe', href: 'https://online.omdnl.org/central-bank', external: true, accent: 'green' },
{ id: 'pub-o24', label: 'Office 24 Central Bank', description: 'office24.omdnl.org', icon: 'office', href: 'https://office24.omdnl.org/office-24', external: true, accent: 'purple' },
{ id: 'pub-trade', label: 'Exchange', description: 'exchange.omdnl.org', icon: 'trade', href: 'https://exchange.omdnl.org/trade', external: true, accent: 'gold' },
{ id: 'pub-forex', label: 'Forex', description: 'forex.omdnl.org', icon: 'trade', href: 'https://forex.omdnl.org/trade', external: true, accent: 'gold' },
{ id: 'explorer', label: 'Chain 138 Explorer', description: 'Blocks, contracts, transactions', icon: 'globe', href: defaultFrontendExplorerUrl, external: true, accent: 'slate' },
];
/** Public Z Ecosystem portal — separate from OMNL (zblockchainsystem.com) */
export const Z_ECOSYSTEM_PORTAL_LINKS: EcosystemLink[] = [
{ id: 'z-hub', label: 'Z Ecosystem', description: 'zblockchainsystem.com', icon: 'globe', href: 'https://zblockchainsystem.com/z', external: true, accent: 'purple' },
{ id: 'z-wallet', label: 'International Z Wallet', description: 'Z Chain wallet + corridors', icon: 'wallet', href: 'https://zblockchainsystem.com/z-wallet', external: true, accent: 'blue' },
{ id: 'z-bot', label: 'Z Bot', description: 'In-app assistant', icon: 'terminal', href: 'https://zblockchainsystem.com/z-wallet#z-bot', external: true, accent: 'teal' },
{ id: 'z-swap', label: 'Digital Swap', description: 'zblockchainsystem.com', icon: 'swap', href: 'https://zblockchainsystem.com/swap', external: true, accent: 'teal' },
{ id: 'z-bank', label: 'Z Online Bank', description: 'M0M4 settlement UI', icon: 'central-bank', href: 'https://zblockchainsystem.com/central-bank', external: true, accent: 'green' },
];
/** API & docs endpoints — same-origin via portal-serve proxy on tunnel/local */
export const API_LINKS: EcosystemLink[] = [
{ id: 'openapi', label: 'OpenAPI JSON', description: 'Machine-readable OMNL API spec', icon: 'code', href: '/api/v1/omnl/openapi.json', accent: 'teal' },
{ id: 'catalog', label: 'API Catalog', description: 'All OMNL routes and auth notes', icon: 'ledger', href: '/api/v1/omnl/catalog', accent: 'blue' },
{ id: 'integration', label: 'Integration Status', description: 'Configured env groups (no secrets)', icon: 'check', href: '/api/v1/omnl/integration-status', accent: 'green' },
{ id: 'registry', label: 'IPSAS Registry', description: 'GL registry JSON', icon: 'money', href: '/api/v1/omnl/ipsas/registry', accent: 'gold' },
{ id: 'settlement-health', label: 'Settlement health', description: '128-chain settlement middleware', icon: 'check', href: '/settlement/health', accent: 'green' },
{ id: 'exchange-health', label: 'Exchange health', description: 'DBIS exchange service', icon: 'check', href: '/exchange/health', accent: 'green' },
{ id: 'fineract-swagger', label: 'Fineract Swagger', description: 'omnl.hybxfinance.io', icon: 'chart', href: 'https://omnl.hybxfinance.io/fineract-provider/swagger-ui/index.html', external: true, accent: 'purple' },
];

View File

@@ -0,0 +1,9 @@
/** Build profile: `z` = Z Blockchain System only; `omnl` = DBIS / OMNL only */
export type EcosystemMode = 'z' | 'omnl';
const raw = (import.meta.env.VITE_ECOSYSTEM as string | undefined)?.trim().toLowerCase();
export const ECOSYSTEM_MODE: EcosystemMode = raw === 'z' ? 'z' : 'omnl';
export const isZEcosystem = ECOSYSTEM_MODE === 'z';
export const isOmnlEcosystem = ECOSYSTEM_MODE === 'omnl';

View File

@@ -0,0 +1,46 @@
/** GUOSMM gazette notices — SSOT: config/guosmm-notices.v1.json */
export type GuosmmNoticeLink = {
documentId: string;
title: string;
url: string;
series: string;
entity?: string;
fineractOfficeId?: number;
};
export const GUOSMM_GAZETTE_BASE = 'https://gazette.shosjj.org/notices/2026';
export const GUOSMM_NOTICES: GuosmmNoticeLink[] = [
{
documentId: 'GUOSMM-B-2026-001',
series: 'B',
title: 'Central Bank Charter — OMNL',
url: `${GUOSMM_GAZETTE_BASE}/GUOSMM-B-2026-001`,
},
{
documentId: 'GUOSMM-C-2026-003',
series: 'C',
title: 'zBank — ISB Operations Charter',
url: `${GUOSMM_GAZETTE_BASE}/GUOSMM-C-2026-003`,
entity: 'zBank',
fineractOfficeId: 29,
},
{
documentId: 'GUOSMM-C-2026-004',
series: 'C',
title: 'NovaBank — ISB Operations Charter',
url: `${GUOSMM_GAZETTE_BASE}/GUOSMM-C-2026-004`,
entity: 'NovaBank',
fineractOfficeId: 30,
},
{
documentId: 'GUOSMM-C-2026-005',
series: 'C',
title: 'AnakaBank — ISB Operations Charter',
url: `${GUOSMM_GAZETTE_BASE}/GUOSMM-C-2026-005`,
entity: 'AnakaBank',
fineractOfficeId: 31,
},
];
export const GUOSMM_API_CATALOG = '/api/v1/omnl/gazette/notices';

View File

@@ -13,6 +13,19 @@ import {
optimism,
} from 'wagmi/chains'
import {
SHIVA_ONEX_CHAIN_ID,
shivaOnexExplorerUrl,
shivaOnexRpcUrl,
} from './onexShiva'
import {
Z_CHAIN_ID,
zChainExplorerUrl,
zChainRpcUrl,
} from './zChain'
const rpcUrlShivaOneX = shivaOnexRpcUrl
const rpcUrlZChain = zChainRpcUrl
const rpcUrl138 = import.meta.env.VITE_RPC_URL_138 || 'https://rpc-http-pub.d-bis.org'
const rpcUrl651940 = import.meta.env.VITE_RPC_URL_651940 || 'https://mainnet-rpc.alltra.global'
const rpcUrl42793 = import.meta.env.VITE_RPC_URL_42793 || 'https://node.mainnet.etherlink.com'
@@ -74,6 +87,54 @@ export const chain138 = defineChain({
},
})
/** Chain 900001 - Shiva Blockchain (OneX Wallet / OneX Bank) */
export const shivaOneXChain = defineChain({
id: SHIVA_ONEX_CHAIN_ID,
name: 'Shiva Blockchain (OneX)',
network: 'shiva-onex',
nativeCurrency: {
decimals: 18,
name: 'Shiva',
symbol: 'SHIVA',
},
rpcUrls: {
default: { http: [rpcUrlShivaOneX] },
public: { http: [rpcUrlShivaOneX] },
},
blockExplorers: {
default: {
name: 'Shiva OneX Explorer',
url: shivaOnexExplorerUrl,
},
},
})
export const shivaOneXChainId = shivaOneXChain.id
/** Chain 900002 - Z Chain (International Z Wallet) */
export const zChain = defineChain({
id: Z_CHAIN_ID,
name: 'Z Chain',
network: 'z-chain',
nativeCurrency: {
decimals: 18,
name: 'Z',
symbol: 'Z',
},
rpcUrls: {
default: { http: [rpcUrlZChain] },
public: { http: [rpcUrlZChain] },
},
blockExplorers: {
default: {
name: 'Z Chain Explorer',
url: zChainExplorerUrl,
},
},
})
export const zChainId = zChain.id
/** Chain 651940 - ALL Mainnet */
export const allMainnet = defineChain({
id: 651940,
@@ -165,6 +226,8 @@ export const gnosis = defineChain({
const customChainsWithout2138 = [
chain138,
shivaOneXChain,
zChain,
allMainnet,
etherlink,
bsc,
@@ -175,6 +238,8 @@ const customChainsWithout2138 = [
const customChainsWith2138 = [
chain138,
shivaOneXChain,
zChain,
chain2138Testnet,
allMainnet,
etherlink,
@@ -191,8 +256,8 @@ export const customChains = chain2138TestnetEnabled
/** Source chains the frontend can intentionally target for wallet UX. */
export const frontendSourceChains = chain2138TestnetEnabled
? [chain138, chain2138Testnet] as const
: [chain138] as const
? ([chain138, shivaOneXChain, zChain, chain2138Testnet] as const)
: ([chain138, shivaOneXChain, zChain] as const)
/** Shared source-chain IDs for connect/switch prompts. */
export const frontendSourceChainIds: number[] = frontendSourceChains.map((chain) => chain.id)
@@ -217,6 +282,8 @@ export const allNetworks = [...customChains, ...standardChains] as const
/** Chain IDs we support (for CowSwap, Jumper, UI filters) - all deployed contract networks */
export const supportedChainIds = [
chain138.id,
shivaOneXChain.id,
zChain.id,
...(chain2138TestnetEnabled ? [chain2138Testnet.id] : []),
allMainnet.id,
etherlink.id,
@@ -235,6 +302,8 @@ export const supportedChainIds = [
export const chainRpcUrls: Record<number, string> = {
[mainnet.id]: import.meta.env.VITE_MAINNET_RPC_URL || 'https://ethereum.publicnode.com',
[chain138.id]: rpcUrl138,
[shivaOneXChain.id]: rpcUrlShivaOneX,
[zChain.id]: rpcUrlZChain,
...(chain2138TestnetEnabled ? { [chain2138Testnet.id]: rpcUrl2138 } : {}),
[allMainnet.id]: rpcUrl651940,
[etherlink.id]: rpcUrl42793,

View File

@@ -86,6 +86,8 @@ export const INTERNAL_PATH_GROUPS: InternalPathGroup[] = [
label: 'OMNL API',
entries: [
{ method: 'GET', path: '/api/v1/omnl/catalog', auth: 'none' },
{ method: 'GET', path: '/api/v1/omnl/gazette/notices', auth: 'none', notes: 'GUOSMM official gazette registry' },
{ method: 'GET', path: '/api/v1/omnl/gazette/notices/:documentId', auth: 'none' },
{ method: 'GET', path: '/api/v1/omnl/openapi.json', auth: 'none' },
{ method: 'GET', path: '/api/v1/omnl/health', auth: 'omnl-key-in-production' },
{ method: 'GET', path: '/api/v1/omnl/ipsas/fineract-health', auth: 'omnl-key-in-production' },

View File

@@ -0,0 +1,24 @@
import onexShivaConfig from '../../../config/onex-shiva-integration.v1.json';
export type OnexShivaConfig = typeof onexShivaConfig;
export const onexShiva = onexShivaConfig;
export const SHIVA_ONEX_CHAIN_ID = Number(
import.meta.env.VITE_SHIVA_ONEX_CHAIN_ID || onexShiva.chain.chainId,
);
export const shivaOnexRpcUrl =
import.meta.env.VITE_RPC_URL_900001 ||
import.meta.env.VITE_SHIVA_ONEX_RPC_URL ||
onexShiva.chain.rpcDefault;
export const shivaOnexExplorerUrl =
import.meta.env.VITE_SHIVA_ONEX_EXPLORER_URL || onexShiva.chain.explorerUrl;
export const onexWalletEnabled =
import.meta.env.VITE_ENABLE_ONEX_SHIVA_WALLET !== 'false' &&
import.meta.env.VITE_ENABLE_ONEX_SHIVA_WALLET !== '0';
export const ONEX_BANK_OFFICE_ID = onexShiva.fineract.officeId;
export const ONEX_HYBX_WALLET_ID = onexShiva.fineract.hybxWalletId;

View File

@@ -0,0 +1,21 @@
import zChainConfig from '../../../config/z-chain-integration.v1.json';
export type ZChainConfig = typeof zChainConfig;
export const zChain = zChainConfig;
export const Z_CHAIN_ID = Number(import.meta.env.VITE_Z_CHAIN_ID || zChain.chain.chainId);
export const zChainRpcUrl =
import.meta.env.VITE_RPC_URL_900002 ||
import.meta.env.VITE_Z_CHAIN_RPC_URL ||
zChain.chain.rpcDefault;
export const zChainExplorerUrl =
import.meta.env.VITE_Z_CHAIN_EXPLORER_URL || zChain.chain.explorerUrl;
export const zWalletEnabled =
import.meta.env.VITE_ENABLE_Z_WALLET !== 'false' && import.meta.env.VITE_ENABLE_Z_WALLET !== '0';
export const Z_WALLET_OFFICE_ID = zChain.fineract.officeId;
export const Z_HYBX_WALLET_ID = zChain.fineract.hybxWalletId;

View File

@@ -0,0 +1,11 @@
import zProd from '../../../config/z-ecosystem-production-domains.v1.json';
/** Canonical public origin for Z Ecosystem (not OMNL). */
export const Z_ECOSYSTEM_PUBLIC_URL =
import.meta.env.VITE_Z_ECOSYSTEM_URL?.trim() || zProd.publicUrl;
export function zEcosystemUrl(path: string): string {
const base = Z_ECOSYSTEM_PUBLIC_URL.replace(/\/$/, '');
const suffix = path.startsWith('/') ? path : `/${path}`;
return `${base}${suffix}`;
}

View File

@@ -1,109 +1,214 @@
import { useEffect, useState } from 'react';
import { fetchOmnlChainRegistry, type ChainRegistryResponse, type OmnlChainSummary } from '../../config/supported-chains';
import NasaIcon, { StatusIcon } from '../../components/icons/NasaIcon';
type Filter = 'all' | 'active' | 'primary' | 'l2';
export default function ChainMatrixPanel() {
const [registry, setRegistry] = useState<ChainRegistryResponse | null>(null);
const [filter, setFilter] = useState<Filter>('active');
const [search, setSearch] = useState('');
useEffect(() => {
fetchOmnlChainRegistry().then(setRegistry);
}, []);
const chains = (registry?.chains ?? []).filter((c) => {
if (filter === 'active' && c.status !== 'active') return false;
if (filter === 'primary' && c.omnlRole !== 'settlement-hub' && c.omnlRole !== 'mirror-target') return false;
if (filter === 'l2' && c.tier !== 'l2') return false;
if (search) {
const q = search.toLowerCase();
return c.name.toLowerCase().includes(q) || String(c.chainId).includes(q) || c.nativeSymbol.toLowerCase().includes(q);
}
return true;
});
return (
<section id="z-settlement-protocols-chains" className="dash-card cb-card lg:col-span-2 scroll-mt-20">
<div className="flex flex-wrap items-center justify-between gap-3 mb-4">
<div>
<h2 className="dash-section-title flex items-center gap-2">
<NasaIcon name="network" size={14} className="nasa-icon--telemetry" />
128-chain network matrix
</h2>
<p className="text-xs text-[#7a9bb8] font-mono mt-1">
{registry?.stats?.active ?? 0} active · {registry?.stats?.staged ?? 0} staged ·{' '}
{registry?.stats?.reserved ?? 0} reserved · primary {registry?.primaryChainId ?? 138}
</p>
</div>
<div className="flex items-center gap-2">
<NasaIcon name="search" size={14} className="nasa-icon--telemetry" />
<input
type="search"
placeholder="Search chain…"
value={search}
onChange={(e) => setSearch(e.target.value)}
className="nasa-input !min-h-[36px] !w-48 text-sm"
/>
</div>
</div>
<div className="flex gap-2 mb-4 flex-wrap">
{(['all', 'active', 'primary', 'l2'] as Filter[]).map((f) => (
<button
key={f}
type="button"
onClick={() => setFilter(f)}
className={`px-3 py-1 text-xs capitalize font-mono border ${
filter === f
? 'border-[#00d4ff] text-[#00d4ff] bg-[#00d4ff]/10'
: 'border-[#1e4a8a] text-[#7a9bb8]'
}`}
>
{f}
</button>
))}
</div>
<div className="overflow-x-auto max-h-[360px] overflow-y-auto">
<table className="w-full text-sm nasa-table">
<thead className="sticky top-0">
<tr className="text-left">
<th className="py-2 pr-3">ID</th>
<th className="py-2 pr-3">Network</th>
<th className="py-2 pr-3">Role</th>
<th className="py-2 pr-3">Status</th>
<th className="py-2 pr-3">Swap</th>
<th className="py-2">Bridge</th>
</tr>
</thead>
<tbody>
{chains.map((c: OmnlChainSummary) => (
<tr key={c.chainId}>
<td className="py-1.5 pr-3 font-mono text-[#00d4ff]">{c.chainId}</td>
<td className="py-1.5 pr-3 text-[#e8f4ff]">{c.name}</td>
<td className="py-1.5 pr-3 text-xs text-[#7a9bb8]">{c.omnlRole}</td>
<td className="py-1.5 pr-3">
<span
className={`text-xs px-1.5 py-0.5 font-mono uppercase ${
c.status === 'active'
? 'text-[#3dff8a] border border-[#3dff8a]/35'
: c.status === 'staged'
? 'text-[#ffb347] border border-[#ffb347]/35'
: 'text-[#7a9bb8] border border-[#7a9bb8]/35'
}`}
>
{c.status}
</span>
</td>
<td className="py-1.5 pr-3"><StatusIcon ok={!!c.settlement?.swapEnabled} /></td>
<td className="py-1.5"><StatusIcon ok={!!c.settlement?.bridgeEnabled} /></td>
</tr>
))}
</tbody>
</table>
</div>
</section>
);
}
import { useEffect, useState } from 'react';
import { fetchOmnlChainRegistry, type ChainRegistryResponse, type OmnlChainSummary } from '../../config/supported-chains';
import NasaIcon, { StatusIcon } from '../../components/icons/NasaIcon';
type Filter = 'all' | 'active' | 'primary' | 'l2';
export default function ChainMatrixPanel() {
const [registry, setRegistry] = useState<ChainRegistryResponse | null>(null);
const [filter, setFilter] = useState<Filter>('active');
const [search, setSearch] = useState('');
useEffect(() => {
fetchOmnlChainRegistry().then(setRegistry);
}, []);
const chains = (registry?.chains ?? []).filter((c) => {
if (filter === 'active' && c.status !== 'active') return false;
if (filter === 'primary' && c.omnlRole !== 'settlement-hub' && c.omnlRole !== 'mirror-target') return false;
if (filter === 'l2' && c.tier !== 'l2') return false;
if (search) {
const q = search.toLowerCase();
return c.name.toLowerCase().includes(q) || String(c.chainId).includes(q) || c.nativeSymbol.toLowerCase().includes(q);
}
return true;
});
return (
<section className="dash-card cb-card lg:col-span-2">
<div className="flex flex-wrap items-center justify-between gap-3 mb-4">
<div>
<h2 className="dash-section-title flex items-center justify-center gap-2">
<NasaIcon name="network" size={14} className="nasa-icon--telemetry" />
128-chain network matrix
</h2>
<p className="text-xs text-[#7a9bb8] font-mono mt-1">
{registry?.stats?.active ?? 0} active · {registry?.stats?.staged ?? 0} staged ·{' '}
{registry?.stats?.reserved ?? 0} reserved · primary {registry?.primaryChainId ?? 138}
</p>
</div>
<div className="flex items-center gap-2">
<NasaIcon name="search" size={14} className="nasa-icon--telemetry" />
<input
type="search"
placeholder="Search chain…"
value={search}
onChange={(e) => setSearch(e.target.value)}
className="nasa-input !min-h-[36px] !w-48 text-sm"
/>
</div>
</div>
<div className="flex gap-2 mb-4 flex-wrap">
{(['all', 'active', 'primary', 'l2'] as Filter[]).map((f) => (
<button
key={f}
type="button"
onClick={() => setFilter(f)}
className={`px-3 py-1 text-xs capitalize rounded border ${
filter === f
? 'border-green-500 text-green-400 bg-green-500/10'
: 'border-white/10 text-[#848e9c]'
}`}
>
{f}
</button>
))}
</div>
<div className="overflow-x-auto max-h-[360px] overflow-y-auto">
<table className="w-full text-sm nasa-table">
<thead className="sticky top-0">
<tr className="text-left">
<th className="py-2 pr-3">ID</th>
<th className="py-2 pr-3">Network</th>
<th className="py-2 pr-3">Role</th>
<th className="py-2 pr-3">Status</th>
<th className="py-2 pr-3">Swap</th>
<th className="py-2">Bridge</th>
</tr>
</thead>
<tbody>
{chains.map((c: OmnlChainSummary) => (
<tr key={c.chainId}>
<td className="py-1.5 pr-3 font-mono text-[#4ade80]">{c.chainId}</td>
<td className="py-1.5 pr-3 text-[#e8f4ff]">{c.name}</td>
<td className="py-1.5 pr-3 text-xs text-[#7a9bb8]">{c.omnlRole}</td>
<td className="py-1.5 pr-3">
<span
className={`text-xs px-1.5 py-0.5 font-mono uppercase ${
c.status === 'active'
? 'text-[#3dff8a] border border-[#3dff8a]/35'
: c.status === 'staged'
? 'text-[#ffb347] border border-[#ffb347]/35'
: 'text-[#7a9bb8] border border-[#7a9bb8]/35'
}`}
>
{c.status}
</span>
</td>
<td className="py-1.5 pr-3"><StatusIcon ok={!!c.settlement?.swapEnabled} /></td>
<td className="py-1.5"><StatusIcon ok={!!c.settlement?.bridgeEnabled} /></td>
</tr>
))}
</tbody>
</table>
</div>
</section>
);
}

View File

@@ -67,14 +67,9 @@ export function useCentralBank() {
setOffice(o);
setTokenCount(Array.isArray(m2?.tokens) ? m2.tokens.length : 0);
setMonitor(mon);
setMoneySupply(
ms ??
(h?.status === 'ok'
? { fineractConnected: true, fineractLive: false, officeId: 24 }
: null),
);
setMoneySupply(ms);
if (!h && !o) {
setError('Settlement middleware unreachable');
setError('Settlement middleware unreachable — check production API at explorer.d-bis.org');
}
} catch (e) {
setError(e instanceof Error ? e.message : 'Failed to load central bank data');

View File

@@ -0,0 +1,29 @@
import type { ReactNode } from 'react';
import type { NasaIconName } from '../../components/icons/NasaIcon';
import NasaIcon from '../../components/icons/NasaIcon';
type Props = {
title: string;
subtitle?: string;
icon: NasaIconName;
defaultOpen?: boolean;
children: ReactNode;
};
export default function DashboardSectionFold({ title, subtitle, icon, defaultOpen = true, children }: Props) {
return (
<details className="dbis-section-fold" open={defaultOpen}>
<summary className="dbis-section-fold__summary">
<span className="inline-flex items-center justify-center w-8 h-8 rounded-lg bg-green-500/10 border border-green-500/25 text-green-400 shrink-0">
<NasaIcon name={icon} size={16} />
</span>
<span>
{title}
{subtitle && <span className="block text-xs font-normal text-[var(--nasa-muted)] mt-0.5">{subtitle}</span>}
</span>
<NasaIcon name="arrow-right" size={14} className="dbis-section-fold__chevron ml-auto text-[var(--nasa-muted)]" />
</summary>
<div className="dbis-section-fold__body">{children}</div>
</details>
);
}

View File

@@ -0,0 +1,128 @@
import { Link } from 'react-router-dom';
import NasaIcon, { type NasaIconName } from '../../components/icons/NasaIcon';
import type { EcosystemLink, EcosystemLinkAccent } from '../../config/ecosystemLinks';
import { ecosystemLinkUsesAnchor } from '../../config/ecosystemLinkRouting';
type Props = {
title: string;
subtitle?: string;
links: EcosystemLink[];
columns?: '2' | '3';
centered?: boolean;
sectionIcon?: NasaIconName;
variant?: 'grid' | 'list';
hideHeader?: boolean;
};
const ACCENT_STYLES: Record<EcosystemLinkAccent, { tile: string; icon: string }> = {
green: { tile: 'bg-green-500/15 border-green-500/30 shadow-[0_0_20px_rgba(34,197,94,0.1)]', icon: 'text-green-400' },
teal: { tile: 'bg-teal-500/15 border-teal-500/30 shadow-[0_0_20px_rgba(20,184,166,0.1)]', icon: 'text-teal-400' },
blue: { tile: 'bg-sky-500/15 border-sky-500/30 shadow-[0_0_20px_rgba(56,189,248,0.1)]', icon: 'text-sky-400' },
gold: { tile: 'bg-amber-500/15 border-amber-500/30 shadow-[0_0_20px_rgba(245,158,11,0.1)]', icon: 'text-amber-400' },
purple: { tile: 'bg-violet-500/15 border-violet-500/30 shadow-[0_0_20px_rgba(139,92,246,0.1)]', icon: 'text-violet-400' },
slate: { tile: 'bg-slate-500/15 border-slate-500/30', icon: 'text-slate-300' },
};
function LinkCard({ link, variant }: { link: EcosystemLink; variant: 'grid' | 'list' }) {
const accent = link.accent ?? 'green';
const styles = ACCENT_STYLES[accent];
const isExternal = link.external || link.href.startsWith('http');
const useAnchor = ecosystemLinkUsesAnchor(link);
const isList = variant === 'list';
const className = [
'hub-link-card group flex no-underline text-inherit rounded-xl border border-white/10 bg-[var(--nasa-panel)]',
'hover:border-green-500/40 hover:bg-[var(--nasa-panel-elevated)] active:scale-[0.98] transition-all duration-150 shadow-sm',
isList
? 'hub-link-card--list flex-row items-center text-left gap-3 p-3 min-h-[60px]'
: 'flex-col items-center text-center gap-3 p-4 min-h-[148px] hover:-translate-y-0.5',
].join(' ');
const inner = (
<>
<span
className={`hub-link-card__icon inline-flex items-center justify-center shrink-0 rounded-2xl border ${styles.tile} ${styles.icon} ${
isList ? 'w-10 h-10' : 'w-14 h-14'
}`}
>
<NasaIcon name={link.icon} size={isList ? 20 : 26} />
</span>
<div className={`hub-link-card__text ${isList ? 'flex-1 min-w-0' : 'flex-1 w-full'}`}>
<div className={`flex items-center gap-1.5 ${isList ? '' : 'justify-center'}`}>
<h3 className="text-sm font-semibold text-[var(--nasa-text)]">{link.label}</h3>
{isExternal && <NasaIcon name="globe" size={11} className="text-[var(--nasa-muted)] opacity-70 shrink-0" />}
</div>
<p className={`text-xs text-[var(--nasa-muted)] leading-relaxed ${isList ? 'mt-0.5 truncate' : 'mt-1.5 line-clamp-2'}`}>
{link.description}
</p>
</div>
{!isList && (
<span className="hub-link-card__open inline-flex items-center gap-1 text-xs font-medium text-green-500/80 group-hover:text-green-400">
Open
<NasaIcon name="arrow-right" size={12} />
</span>
)}
{isList && <NasaIcon name="arrow-right" size={14} className="text-[var(--nasa-muted)] shrink-0" />}
</>
);
if (isExternal) {
return (
<a href={link.href} target="_blank" rel="noopener noreferrer" className={className}>
{inner}
</a>
);
}
if (useAnchor) {
return (
<a href={link.href} className={className}>
{inner}
</a>
);
}
return (
<Link to={link.href} className={className}>
{inner}
</Link>
);
}
export default function EcosystemLinkGrid({
title,
subtitle,
links,
columns = '3',
centered = true,
sectionIcon = 'link',
variant = 'grid',
hideHeader = false,
}: Props) {
const gridClass =
variant === 'list'
? 'grid grid-cols-1 gap-2'
: columns === '2'
? 'grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4'
: 'grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3 sm:gap-4';
return (
<section className="mb-6 md:mb-10">
{!hideHeader && (
<div className={`mb-4 md:mb-5 hidden md:block${centered ? ' text-center' : ''}`}>
<h2 className="dash-section-title flex items-center justify-center gap-2 text-base">
<span className="inline-flex items-center justify-center w-8 h-8 rounded-lg bg-green-500/10 border border-green-500/25 text-green-400">
<NasaIcon name={sectionIcon} size={16} />
</span>
{title}
</h2>
{subtitle && <p className="dash-section-subtitle mt-1">{subtitle}</p>}
</div>
)}
<div className={gridClass}>
{links.map((link) => (
<LinkCard key={link.id} link={link} variant={variant} />
))}
</div>
</section>
);
}

Some files were not shown because too many files have changed in this diff Show More