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
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:
@@ -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
|
||||
|
||||
128
config/ecosystem-links.v1.json
Normal file
128
config/ecosystem-links.v1.json
Normal 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"
|
||||
}
|
||||
}
|
||||
37
config/genesis-z-chain-900002.json
Normal file
37
config/genesis-z-chain-900002.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
115
config/guosmm-notices.v1.json
Normal file
115
config/guosmm-notices.v1.json
Normal 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"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
22
config/offices/office-29-zbank.v1.json
Normal file
22
config/offices/office-29-zbank.v1.json
Normal 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" }
|
||||
}
|
||||
}
|
||||
22
config/offices/office-30-novabank.v1.json
Normal file
22
config/offices/office-30-novabank.v1.json
Normal 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" }
|
||||
}
|
||||
}
|
||||
22
config/offices/office-31-anakabank.v1.json
Normal file
22
config/offices/office-31-anakabank.v1.json
Normal 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" }
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
46
config/onex-shiva-integration.v1.json
Normal file
46
config/onex-shiva-integration.v1.json
Normal 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"]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
110
config/swift-listener-chain138.v1.json
Normal file
110
config/swift-listener-chain138.v1.json
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
72
config/swift-mt-catalog.v1.json
Normal file
72
config/swift-mt-catalog.v1.json
Normal 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" }
|
||||
}
|
||||
}
|
||||
17
config/z-chain-deployed.v1.json
Normal file
17
config/z-chain-deployed.v1.json
Normal 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"
|
||||
]
|
||||
}
|
||||
105
config/z-chain-integration.v1.json
Normal file
105
config/z-chain-integration.v1.json
Normal 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"
|
||||
}
|
||||
}
|
||||
29
config/z-chain-network-config.json
Normal file
29
config/z-chain-network-config.json
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
37
config/z-chain-production.v1.json
Normal file
37
config/z-chain-production.v1.json
Normal 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"
|
||||
}
|
||||
}
|
||||
35
config/z-ecosystem-links.v1.json
Normal file
35
config/z-ecosystem-links.v1.json
Normal 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"
|
||||
}
|
||||
}
|
||||
28
config/z-ecosystem-production-domains.v1.json
Normal file
28
config/z-ecosystem-production-domains.v1.json
Normal 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"
|
||||
}
|
||||
}
|
||||
23
config/z-ecosystem.local.env.example
Normal file
23
config/z-ecosystem.local.env.example
Normal 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
|
||||
25
config/z-ecosystem.production.env.example
Normal file
25
config/z-ecosystem.production.env.example
Normal 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
|
||||
50
config/z-ecosystem.v1.json
Normal file
50
config/z-ecosystem.v1.json
Normal 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": "M0–M4 banking and settlement rails"
|
||||
}
|
||||
],
|
||||
"integrationRef": "config/z-chain-integration.v1.json",
|
||||
"productionDomainsRef": "config/z-ecosystem-production-domains.v1.json"
|
||||
}
|
||||
27
config/z-supported-chains.v1.json
Normal file
27
config/z-supported-chains.v1.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
39
contracts/z-chain/AccountWalletRegistryZ.sol
Normal file
39
contracts/z-chain/AccountWalletRegistryZ.sol
Normal 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];
|
||||
}
|
||||
}
|
||||
18
contracts/z-chain/ZChainUSDC.sol
Normal file
18
contracts/z-chain/ZChainUSDC.sol
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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/;
|
||||
|
||||
56
deploy/nginx/z-ecosystem-common-locations.conf
Normal file
56
deploy/nginx/z-ecosystem-common-locations.conf
Normal 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;
|
||||
}
|
||||
49
deploy/nginx/z-ecosystem-domains.conf
Normal file
49
deploy/nginx/z-ecosystem-domains.conf
Normal 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;
|
||||
}
|
||||
}
|
||||
157
docs/04-configuration/Z_ECOSYSTEM.md
Normal file
157
docs/04-configuration/Z_ECOSYSTEM.md
Normal 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`.
|
||||
|
||||
@@ -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
|
||||
|
||||
---
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
6
frontend-dapp/.env.local.example
Normal file
6
frontend-dapp/.env.local.example
Normal 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
|
||||
@@ -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>
|
||||
|
||||
@@ -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",
|
||||
|
||||
84
frontend-dapp/public/omnl/compliance/index.html
Normal file
84
frontend-dapp/public/omnl/compliance/index.html
Normal 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 & 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 & 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>
|
||||
55
frontend-dapp/public/omnl/dashboard/index.html
Normal file
55
frontend-dapp/public/omnl/dashboard/index.html
Normal 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>
|
||||
8
frontend-dapp/public/omnl/static/omnl-api-base.js
Normal file
8
frontend-dapp/public/omnl/static/omnl-api-base.js
Normal 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';
|
||||
};
|
||||
})();
|
||||
219
frontend-dapp/public/omnl/static/omnl-compliance-console.css
Normal file
219
frontend-dapp/public/omnl/static/omnl-compliance-console.css
Normal 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; }
|
||||
}
|
||||
255
frontend-dapp/public/omnl/static/omnl-compliance-console.js
Normal file
255
frontend-dapp/public/omnl/static/omnl-compliance-console.js
Normal 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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
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();
|
||||
})();
|
||||
190
frontend-dapp/public/omnl/static/omnl-settlement-terminal.css
Normal file
190
frontend-dapp/public/omnl/static/omnl-settlement-terminal.css
Normal 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);
|
||||
}
|
||||
219
frontend-dapp/public/omnl/static/omnl-settlement-terminal.js
Normal file
219
frontend-dapp/public/omnl/static/omnl-settlement-terminal.js
Normal 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');
|
||||
})();
|
||||
76
frontend-dapp/public/omnl/terminal/index.html
Normal file
76
frontend-dapp/public/omnl/terminal/index.html
Normal 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>
|
||||
16
frontend-dapp/scripts/build-z.mjs
Normal file
16
frontend-dapp/scripts/build-z.mjs
Normal 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);
|
||||
142
frontend-dapp/scripts/portal-serve.mjs
Normal file
142
frontend-dapp/scripts/portal-serve.mjs
Normal 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}`);
|
||||
});
|
||||
73
frontend-dapp/scripts/sync-omnl-consoles.mjs
Normal file
73
frontend-dapp/scripts/sync-omnl-consoles.mjs
Normal 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/');
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
43
frontend-dapp/src/components/layout/MobileBottomNav.tsx
Normal file
43
frontend-dapp/src/components/layout/MobileBottomNav.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
80
frontend-dapp/src/components/layout/MobileNavDrawer.tsx
Normal file
80
frontend-dapp/src/components/layout/MobileNavDrawer.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
41
frontend-dapp/src/components/layout/ZMobileBottomNav.tsx
Normal file
41
frontend-dapp/src/components/layout/ZMobileBottomNav.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
84
frontend-dapp/src/components/layout/ZProductLayout.tsx
Normal file
84
frontend-dapp/src/components/layout/ZProductLayout.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -34,7 +34,7 @@ export default function ToastProvider() {
|
||||
},
|
||||
loading: {
|
||||
iconTheme: {
|
||||
primary: '#3b82f6',
|
||||
primary: '#22c55e',
|
||||
secondary: '#fff',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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`,
|
||||
|
||||
@@ -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;
|
||||
|
||||
16
frontend-dapp/src/config/ecosystemLinkRouting.ts
Normal file
16
frontend-dapp/src/config/ecosystemLinkRouting.ts
Normal 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);
|
||||
}
|
||||
77
frontend-dapp/src/config/ecosystemLinks.ts
Normal file
77
frontend-dapp/src/config/ecosystemLinks.ts
Normal 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: 'M0–M4 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' },
|
||||
];
|
||||
9
frontend-dapp/src/config/ecosystemMode.ts
Normal file
9
frontend-dapp/src/config/ecosystemMode.ts
Normal 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';
|
||||
46
frontend-dapp/src/config/guosmmNotices.ts
Normal file
46
frontend-dapp/src/config/guosmmNotices.ts
Normal 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';
|
||||
@@ -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,
|
||||
|
||||
@@ -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' },
|
||||
|
||||
24
frontend-dapp/src/config/onexShiva.ts
Normal file
24
frontend-dapp/src/config/onexShiva.ts
Normal 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;
|
||||
21
frontend-dapp/src/config/zChain.ts
Normal file
21
frontend-dapp/src/config/zChain.ts
Normal 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;
|
||||
11
frontend-dapp/src/config/zEcosystemDomain.ts
Normal file
11
frontend-dapp/src/config/zEcosystemDomain.ts
Normal 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}`;
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
29
frontend-dapp/src/features/hub/DashboardSectionFold.tsx
Normal file
29
frontend-dapp/src/features/hub/DashboardSectionFold.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
128
frontend-dapp/src/features/hub/EcosystemLinkGrid.tsx
Normal file
128
frontend-dapp/src/features/hub/EcosystemLinkGrid.tsx
Normal 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
Reference in New Issue
Block a user