feat: add Z Chain 900002 ecosystem with wallet, Z Bot, and deploy scripts
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m13s
CI/CD Pipeline / Security Scanning (push) Successful in 2m30s
CI/CD Pipeline / Lint and Format (push) Failing after 45s
CI/CD Pipeline / Terraform Validation (push) Failing after 26s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 28s
Deploy ChainID 138 / Deploy ChainID 138 (push) Failing after 39s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (push) Failing after 21s
Validation / validate-genesis (push) Successful in 30s
Validation / validate-terraform (push) Failing after 25s
Validation / validate-kubernetes (push) Failing after 13s
Validation / validate-smart-contracts (push) Failing after 16s
Validation / validate-security (push) Failing after 1m28s
Validation / validate-documentation (push) Failing after 19s
Verify Deployment / Verify Deployment (push) Failing after 53s

Ship Z Chain slot, International Z Wallet routes, in-app Z Bot APIs, Besu bootstrap, and local/production deployment tooling for digital.omdnl.org.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-06 05:25:10 -07:00
parent ecd6120c22
commit e35ad9047c
55 changed files with 2717 additions and 119 deletions

View File

@@ -50,6 +50,18 @@ CHAIN_138_RPC_URL=http://192.168.11.221:8545
# === Mirror chain 651940 ===
CHAIN_651940_RPC_URL=https://mainnet-rpc.alltra.global
# === Z Chain (consumer wallet, chainId 900002) ===
CHAIN_900002_RPC_URL=http://127.0.0.1:8546
OMNL_COMPLIANCE_CORE_900002=
VITE_RPC_URL_900002=http://127.0.0.1:8546
VITE_Z_CHAIN_EXPLORER_URL=https://explorer.z.omdnl.org
# === Z Bot (Azure OpenAI — optional; rule-based fallback when unset) ===
AZURE_OPENAI_ENDPOINT=
AZURE_OPENAI_API_KEY=
AZURE_OPENAI_DEPLOYMENT=gpt-4o-mini
AZURE_OPENAI_API_VERSION=2024-02-15-preview
# === Major L1/L2 RPC (set per chain — CHAIN_<id>_RPC_URL) ===
CHAIN_1_RPC_URL=https://eth.llamarpc.com
CHAIN_56_RPC_URL=https://bsc-dataseed.binance.org

View File

@@ -28,6 +28,9 @@
"reserve": "/reserve",
"history": "/history",
"wallets": "/wallets",
"zEcosystem": "/z",
"zWallet": "/z-wallet",
"zBot": "/z-wallet#z-bot",
"admin": "/admin",
"docs": "/docs"
},
@@ -50,6 +53,9 @@
{ "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": "zEcosystem", "label": "Z Ecosystem", "path": "/z", "group": "app" },
{ "id": "zWallet", "label": "International Z Wallet", "path": "/z-wallet", "group": "app" },
{ "id": "zBot", "label": "Z Bot", "path": "/z-wallet#z-bot", "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" },

View File

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

View File

@@ -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,13 @@
"treasuryWalletMainnet": "0xfc4D24Bda60240D6dfa1a702eE83c65002cf5218",
"treasuryWalletChain138": "0x934adfFFbe4D079b7dbDcE183805dBcBeC7818cb",
"chains": {
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
},
"1": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
},
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
@@ -538,6 +538,10 @@
"treasuryWalletChain138": "0xc93522ae4ab7482572cc29edd68fbb6d5ec6a585",
"treasuryWalletShivaOneX": "0x0000000000000000000000000000000000000000",
"chains": {
"1": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
},
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
@@ -546,9 +550,9 @@
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0x0000000000000000000000000000000000000000"
},
"1": {
"900002": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
"tokenM1": "0x5FbDB2315678afecb367f032d93F642f64180aa3"
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",

View File

@@ -42,7 +42,8 @@
{
"host": "digital.omdnl.org",
"product": "digital-swap",
"defaultRoute": "/swap"
"defaultRoute": "/swap",
"routes": ["/z", "/z-wallet", "/swap", "/hub"]
},
{
"host": "office24.omdnl.org",

View File

@@ -1,4 +1,4 @@
{
{.\scripts\deployment\deploy-z-ecosystem.ps1
"$schema": "OMNL Bank supported chains — 128-chain production registry",
"version": "1.0.0",
"brand": "OMNL Central Bank",
@@ -1244,20 +1244,23 @@
},
{
"chainId": 900002,
"name": "OMNL Expansion Slot 2",
"name": "Z Chain",
"type": "evm",
"nativeSymbol": "ETH",
"tier": "reserved",
"omnlRole": "expansion",
"status": "reserved",
"nativeSymbol": "Z",
"tier": "primary",
"omnlRole": "z-chain",
"status": "active",
"rpcEnv": "CHAIN_900002_RPC_URL",
"explorerUrl": "https://explorer.z.omdnl.org",
"rpcDefault": "http://127.0.0.1:8546",
"settlement": {
"m2LoadEnabled": false,
"swapEnabled": false,
"bridgeEnabled": false,
"officeId": 24
"m2LoadEnabled": true,
"swapEnabled": true,
"bridgeEnabled": true,
"officeId": 29
},
"complianceEnv": "OMNL_COMPLIANCE_CORE_900002"
"complianceEnv": "OMNL_COMPLIANCE_CORE_900002",
"integrationRef": "config/z-chain-integration.v1.json"
},
{
"chainId": 900003,

View File

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

View File

@@ -0,0 +1,105 @@
{
"$schema": "International Z Wallet + Z Chain — OMNL 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.z.omdnl.org",
"omnlRegistrySlot": 900002
},
"fineract": {
"officeId": 29,
"externalId": "ZBANK-OMNL",
"entityName": "zBank",
"hybxWalletId": "HYBX-OFFICE-029",
"m1SpendableGl": "2100"
},
"bankCorridor": {
"orderingName": "International Z Wallet",
"creditorBic": "ZCHNOMNL33",
"debtorIban": "Z298ZCHN000000000000000029",
"swiftMessageKind": "MT103",
"remittanceTemplate": "International Z Wallet → Z Online Bank · Z Chain settlement"
},
"internationalCorridors": [
{
"id": "IQ-USD",
"label": "Iraq (USD)",
"country": "IQ",
"currency": "USD",
"rails": [
"SWIFT",
"HYBX",
"CHAIN_MINT"
]
},
{
"id": "IR-USD",
"label": "Iran (USD)",
"country": "IR",
"currency": "USD",
"rails": [
"HYBX",
"CHAIN_MINT"
]
},
{
"id": "EU-EUR",
"label": "Europe (EUR)",
"country": "EU",
"currency": "EUR",
"rails": [
"SWIFT",
"HYBX"
]
},
{
"id": "GLOBAL-USD",
"label": "Global (USD)",
"country": "GLOBAL",
"currency": "USD",
"rails": [
"SWIFT",
"HYBX",
"CHAIN_MINT"
]
}
],
"wallet": {
"thirdwebMetadataName": "International Z Wallet",
"supportedAuth": [
"email",
"google",
"apple",
"passkey"
],
"defaultTokenLineId": "USD-M2",
"defaultTokenSymbol": "cUSDC"
},
"settlement": {
"zBridgeOfficeId": 24,
"targetChainId": 900002,
"rails": [
"HYBX",
"SWIFT",
"CHAIN_MINT"
]
},
"contracts": {
"cUSDC": "0x5FbDB2315678afecb367f032d93F642f64180aa3",
"accountWalletRegistry": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
"deployRef": "config/z-chain-deployed.v1.json"
}
}

View File

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

View File

@@ -0,0 +1,34 @@
{
"$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": {
"rpcPublic": "https://rpc.z.omdnl.org",
"rpcLan": "http://192.168.11.212:8546",
"explorer": "https://explorer.z.omdnl.org",
"walletPortal": "https://digital.omdnl.org/z-wallet",
"ecosystemHub": "https://digital.omdnl.org/z"
},
"env": {
"rpc": "CHAIN_900002_RPC_URL",
"compliance": "OMNL_COMPLIANCE_CORE_900002",
"viteRpc": "VITE_RPC_URL_900002",
"viteExplorer": "VITE_Z_CHAIN_EXPLORER_URL"
},
"besu": {
"containerName": "z-chain-besu-prod",
"rpcPort": 8546,
"dataPath": "/opt/besu-z-chain",
"genesisPath": "/opt/besu-z-chain/config/genesis.json"
},
"deployScripts": {
"bootstrapLocal": "scripts/deployment/bootstrap-z-chain-local.sh",
"bootstrapProduction": "scripts/deployment/bootstrap-z-chain-production.sh",
"deployContracts": "scripts/deployment/deploy-z-chain-contracts.sh",
"syncAddresses": "scripts/deployment/sync-z-chain-deployed-addresses.mjs"
}
}

View File

@@ -0,0 +1,15 @@
# Z Ecosystem local overrides — copy to z-ecosystem.local.env (gitignored)
# Loaded by scripts/deployment/deploy-z-ecosystem.ps1
# Z Chain (local Hardhat/Besu)
CHAIN_900002_RPC_URL=http://127.0.0.1:8546
# Z Bot — Azure OpenAI (optional; rule-based fallback when unset)
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 middleware (international transfers)
SETTLEMENT_MIDDLEWARE_URL=http://127.0.0.1:3011
SETTLEMENT_MIDDLEWARE_CONFIG=config/settlement-middleware.production.v1.json

View File

@@ -0,0 +1,41 @@
{
"$schema": "Z consumer ecosystem hub links",
"version": "1.0.0",
"updated": "2026-07-05",
"brand": "Z Ecosystem",
"routes": {
"hub": "/z",
"wallet": "/z-wallet",
"bot": "/z-wallet#z-bot",
"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 (OMNL expansion slot 900002)"
},
{
"id": "zOnlineBank",
"label": "Z Online Bank",
"path": "/central-bank",
"description": "M0M4 banking and settlement rails"
}
],
"integrationRef": "config/z-chain-integration.v1.json"
}

View File

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

View File

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

View File

@@ -0,0 +1,149 @@
# 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://rpc.z.omdnl.org``127.0.0.1:8546`
- `https://explorer.z.omdnl.org` → Blockscout (optional, follow Chain 138 explorer pattern)
- Portal routes: `https://digital.omdnl.org/z` and `/z-wallet`
Set on banking portal LXC:
```env
CHAIN_900002_RPC_URL=https://rpc.z.omdnl.org
VITE_RPC_URL_900002=https://rpc.z.omdnl.org
VITE_Z_CHAIN_EXPLORER_URL=https://explorer.z.omdnl.org
```
## Local dev without Docker
If Docker Desktop is not running, use Anvil:
```powershell
.\scripts\deployment\bootstrap-z-chain-anvil.ps1
```
Default Anvil account #0 key: `0xac0974bec39a17e36ba4a590bcafb95bd05417ff81f81088a92f31fcfcf35588`
```bash
export PRIVATE_KEY=0xac0974bec39a17e36ba4a590bcafb95bd05417ff81f81088a92f31fcfcf35588
bash scripts/deployment/deploy-z-chain-contracts.sh
node scripts/deployment/sync-z-chain-deployed-addresses.mjs
```
## Contract addresses
After deploy, sync all configs:
```bash
node scripts/deployment/sync-z-chain-deployed-addresses.mjs
```
Updates `z-chain-deployed.v1.json`, `z-chain-integration.v1.json`, and `hybx-omnl-cross-chain-lines.json`.

View File

@@ -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
@@ -151,6 +157,7 @@ cbor_metadata = false
# Prevents default localhost:8545 when ETH_RPC_URL not set
[rpc_endpoints]
chain138 = "http://192.168.11.211:8545"
zchain = "http://127.0.0.1:8546"
avalanche = "https://avalanche-c-chain.publicnode.com"
arbitrum = "https://arbitrum-one.publicnode.com"
cronos = "https://evm.cronos.org"

View File

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

View File

@@ -0,0 +1,5 @@
# Z Chain local dev — generated by complete-z-chain.ps1
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
CHAIN_900002_RPC_URL=http://127.0.0.1:8546

View File

@@ -0,0 +1,145 @@
/**
* 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 || 'https://exchange.omdnl.org';
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')) {
return proxyRequest(req, res, EXCHANGE_TARGET);
}
if (url.startsWith('/omnl/')) {
if (shouldServeOmnlStatic(url)) {
const file = resolveStatic(url);
if (file) {
const cache = url.startsWith('/omnl/static/') ? 'public, max-age=31536000, immutable' : 'no-cache';
return sendFile(res, file, cache);
}
}
return proxyRequest(req, res, API_TARGET);
}
const file = resolveStatic(url === '/' ? '/index.html' : url);
if (file) {
const cache =
url.startsWith('/assets/') ? 'public, max-age=31536000, immutable' : 'no-cache, no-store, must-revalidate';
return sendFile(res, file, cache);
}
const indexHtml = path.join(DIST, 'index.html');
if (fs.existsSync(indexHtml)) {
return sendFile(res, indexHtml, 'no-cache, no-store, must-revalidate');
}
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not found');
});
if (!fs.existsSync(DIST)) {
console.error(`Missing build output: ${DIST}\nRun: npm run build`);
process.exit(1);
}
server.listen(PORT, () => {
console.log(`portal-serve listening on http://localhost:${PORT}`);
console.log(` dist: ${DIST}`);
console.log(` api: ${API_TARGET}`);
console.log(` settlement: ${SETTLEMENT_TARGET}`);
});

View File

@@ -16,6 +16,8 @@ import AdminPanel from './pages/AdminPanel'
import DocsPage from './pages/DocsPage'
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 ToastProvider from './components/ui/ToastProvider'
@@ -51,6 +53,7 @@ function App() {
<Routes>
<Route element={<OmnlProductLayout />}>
<Route path="/hub" element={<HubPage />} />
<Route path="/z" element={<ZEcosystemHubPage />} />
<Route path="/central-bank" element={<CentralBankPage />} />
<Route path="/office-24" element={<Office24Page />} />
<Route path="/trade" element={<TradePage />} />
@@ -69,6 +72,7 @@ function App() {
<Route path="/admin" element={<AdminPanel />} />
<Route path="/docs" element={<DocsPage />} />
<Route path="/wallets" element={<WalletsDemoPage />} />
<Route path="/z-wallet" element={<ZWalletPage />} />
</Route>
<Route path="/" element={<Navigate to="/hub" replace />} />
</Routes>

View File

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

View File

@@ -18,8 +18,14 @@ import {
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'
@@ -105,6 +111,30 @@ export const shivaOneXChain = defineChain({
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,
@@ -197,6 +227,7 @@ export const gnosis = defineChain({
const customChainsWithout2138 = [
chain138,
shivaOneXChain,
zChain,
allMainnet,
etherlink,
bsc,
@@ -208,6 +239,7 @@ const customChainsWithout2138 = [
const customChainsWith2138 = [
chain138,
shivaOneXChain,
zChain,
chain2138Testnet,
allMainnet,
etherlink,
@@ -224,8 +256,8 @@ export const customChains = chain2138TestnetEnabled
/** Source chains the frontend can intentionally target for wallet UX. */
export const frontendSourceChains = chain2138TestnetEnabled
? ([chain138, shivaOneXChain, chain2138Testnet] as const)
: ([chain138, shivaOneXChain] 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)
@@ -251,6 +283,7 @@ export const allNetworks = [...customChains, ...standardChains] as const
export const supportedChainIds = [
chain138.id,
shivaOneXChain.id,
zChain.id,
...(chain2138TestnetEnabled ? [chain2138Testnet.id] : []),
allMainnet.id,
etherlink.id,
@@ -270,6 +303,7 @@ export const chainRpcUrls: Record<number, string> = {
[mainnet.id]: import.meta.env.VITE_MAINNET_RPC_URL || 'https://ethereum.publicnode.com',
[chain138.id]: rpcUrl138,
[shivaOneXChain.id]: rpcUrlShivaOneX,
[zChain.id]: rpcUrlZChain,
...(chain2138TestnetEnabled ? { [chain2138Testnet.id]: rpcUrl2138 } : {}),
[allMainnet.id]: rpcUrl651940,
[etherlink.id]: rpcUrl42793,

View File

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

View File

@@ -0,0 +1,75 @@
import { useEffect, useRef, useState } from 'react';
import { useZBotChat } from './useZBotChat';
import { zChain } from '../../config/zChain';
import './z-bot.css';
type Props = {
walletAddress?: string;
chainId?: number;
defaultOpen?: boolean;
};
export default function ZBotPanel({ walletAddress, chainId, defaultOpen = false }: Props) {
const [open, setOpen] = useState(defaultOpen);
const [input, setInput] = useState('');
const { messages, sendMessage, loading } = useZBotChat(walletAddress, chainId);
const chatRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (chatRef.current) {
chatRef.current.scrollTop = chatRef.current.scrollHeight;
}
}, [messages, open]);
const handleSend = () => {
const text = input;
setInput('');
void sendMessage(text);
};
return (
<>
<button
type="button"
id="z-bot"
className="z-bot-fab"
onClick={() => setOpen((v) => !v)}
aria-label="Open Z Bot"
>
Z Bot
</button>
{open && (
<div className="z-bot-panel" role="dialog" aria-label="Z Bot chat">
<header className="z-bot-header">
<span>{zChain.brand.bot}</span>
<button type="button" className="z-bot-close" onClick={() => setOpen(false)} aria-label="Close">
×
</button>
</header>
<div ref={chatRef} className="z-bot-chat">
{messages.map((m, i) => (
<div key={i} className={`z-bot-message z-bot-message--${m.role}`}>
<div className="z-bot-message__text">{m.content}</div>
</div>
))}
{loading && <div className="z-bot-message z-bot-message--assistant">Thinking</div>}
</div>
<div className="z-bot-input-row">
<input
type="text"
className="z-bot-input"
placeholder="Ask Z Bot…"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSend()}
/>
<button type="button" className="z-bot-send" onClick={handleSend} disabled={loading}>
Send
</button>
</div>
</div>
)}
</>
);
}

View File

@@ -0,0 +1,71 @@
import { useCallback, useState } from 'react';
import { TOKEN_AGGREGATION_URL } from '../../config/dex';
import { omnlApiHeaders } from '../../config/dex';
export type ZBotMessage = {
role: 'user' | 'assistant';
content: string;
timestamp: Date;
};
function sessionId(): string {
const key = 'z-bot-session';
let id = sessionStorage.getItem(key);
if (!id) {
id = `zbot-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
sessionStorage.setItem(key, id);
}
return id;
}
export function useZBotChat(walletAddress?: string, chainId?: number) {
const [messages, setMessages] = useState<ZBotMessage[]>([
{
role: 'assistant',
content: 'Hi — I am Z Bot. Ask about your balance, international corridors, or transfer status.',
timestamp: new Date(),
},
]);
const [loading, setLoading] = useState(false);
const sendMessage = useCallback(
async (text: string) => {
if (!text.trim()) return;
const userMsg: ZBotMessage = { role: 'user', content: text.trim(), timestamp: new Date() };
setMessages((prev) => [...prev, userMsg]);
setLoading(true);
try {
const res = await fetch(`${TOKEN_AGGREGATION_URL}/z-bot/chat`, {
method: 'POST',
headers: omnlApiHeaders(),
body: JSON.stringify({
sessionId: sessionId(),
message: text.trim(),
walletAddress,
chainId,
}),
});
const data = (await res.json()) as { reply?: string; error?: string };
const reply = data.reply ?? data.error ?? 'Sorry, I could not process that request.';
setMessages((prev) => [
...prev,
{ role: 'assistant', content: reply, timestamp: new Date() },
]);
} catch (e) {
setMessages((prev) => [
...prev,
{
role: 'assistant',
content: e instanceof Error ? e.message : 'Network error',
timestamp: new Date(),
},
]);
} finally {
setLoading(false);
}
},
[walletAddress, chainId],
);
return { messages, sendMessage, loading };
}

View File

@@ -0,0 +1,111 @@
.z-bot-fab {
position: fixed;
right: 1.25rem;
bottom: 1.25rem;
z-index: 50;
padding: 0.65rem 1rem;
border-radius: 9999px;
background: linear-gradient(135deg, #7c3aed, #0d9488);
color: #fff;
font-weight: 600;
font-size: 0.875rem;
border: none;
cursor: pointer;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
}
.z-bot-panel {
position: fixed;
right: 1.25rem;
bottom: 4.5rem;
z-index: 50;
width: min(360px, calc(100vw - 2rem));
height: 420px;
display: flex;
flex-direction: column;
background: #1a1d24;
border: 1px solid rgba(124, 58, 237, 0.35);
border-radius: 1rem;
overflow: hidden;
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.45);
}
.z-bot-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 1rem;
background: #252830;
font-weight: 600;
color: #e8f4ff;
}
.z-bot-close {
background: none;
border: none;
color: #a0a0a0;
font-size: 1.25rem;
cursor: pointer;
line-height: 1;
}
.z-bot-chat {
flex: 1;
overflow-y: auto;
padding: 0.75rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.z-bot-message {
max-width: 90%;
padding: 0.5rem 0.75rem;
border-radius: 0.75rem;
font-size: 0.875rem;
line-height: 1.4;
}
.z-bot-message--user {
align-self: flex-end;
background: #0d9488;
color: #fff;
}
.z-bot-message--assistant {
align-self: flex-start;
background: #2d3140;
color: #e8f4ff;
}
.z-bot-input-row {
display: flex;
gap: 0.5rem;
padding: 0.75rem;
border-top: 1px solid rgba(255, 255, 255, 0.08);
}
.z-bot-input {
flex: 1;
border-radius: 0.5rem;
border: 1px solid rgba(255, 255, 255, 0.1);
background: #252830;
color: #fff;
padding: 0.5rem 0.75rem;
font-size: 0.875rem;
}
.z-bot-send {
border-radius: 0.5rem;
border: none;
background: #7c3aed;
color: #fff;
padding: 0.5rem 0.85rem;
font-weight: 500;
cursor: pointer;
}
.z-bot-send:disabled {
opacity: 0.5;
cursor: not-allowed;
}

View File

@@ -0,0 +1,82 @@
import TransferStatusCard from './TransferStatusCard';
import { useZWalletInternationalTransfer } from './useZWalletInternationalTransfer';
import { useState } from 'react';
type Props = {
recipientAddress?: string;
};
export default function InternationalTransferPanel({ recipientAddress }: Props) {
const { submitTransfer, loading, lastResult, error, corridors } = useZWalletInternationalTransfer();
const [amount, setAmount] = useState('');
const [creditorIban, setCreditorIban] = useState('');
const [corridorId, setCorridorId] = useState(corridors[0]?.id ?? 'GLOBAL-USD');
const selected = corridors.find((c) => c.id === corridorId);
return (
<section className="p-6 bg-[#252830] rounded-xl border border-emerald-500/20 space-y-4 text-left">
<div>
<h2 className="text-lg font-semibold text-white">International transfer</h2>
<p className="text-sm text-[#A0A0A0]">Send abroad via Z Bank rails (SWIFT, HYBX, CHAIN_MINT).</p>
</div>
<label className="block text-sm">
<span className="text-[#A0A0A0]">Corridor</span>
<select
className="mt-1 w-full rounded-lg bg-[#1a1d24] border border-white/10 px-3 py-2 text-white"
value={corridorId}
onChange={(e) => setCorridorId(e.target.value)}
>
{corridors.map((c) => (
<option key={c.id} value={c.id}>
{c.label}
</option>
))}
</select>
</label>
<label className="block text-sm">
<span className="text-[#A0A0A0]">Amount ({selected?.currency ?? 'USD'})</span>
<input
type="text"
inputMode="decimal"
className="mt-1 w-full rounded-lg bg-[#1a1d24] border border-white/10 px-3 py-2 text-white"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="100.00"
/>
</label>
<label className="block text-sm">
<span className="text-[#A0A0A0]">Creditor IBAN</span>
<input
type="text"
className="mt-1 w-full rounded-lg bg-[#1a1d24] border border-white/10 px-3 py-2 text-white font-mono text-xs"
value={creditorIban}
onChange={(e) => setCreditorIban(e.target.value)}
placeholder="GB82WEST12345698765432"
/>
</label>
<button
type="button"
disabled={loading || !amount || !creditorIban}
onClick={() =>
void submitTransfer({
amount,
creditorIban,
corridorId,
currency: selected?.currency ?? 'USD',
recipientAddress,
})
}
className="w-full py-2.5 rounded-lg bg-emerald-600 hover:bg-emerald-500 disabled:opacity-50 text-white font-medium"
>
{loading ? 'Submitting…' : 'Send international transfer'}
</button>
{error && <p className="text-sm text-red-400">{error}</p>}
{lastResult?.settlementId && <TransferStatusCard settlementRef={lastResult.settlementId} />}
</section>
);
}

View File

@@ -0,0 +1,31 @@
import { useZWalletTransferStatus } from './useZWalletTransferStatus';
type Props = {
settlementRef: string;
};
export default function TransferStatusCard({ settlementRef }: Props) {
const { status, loading } = useZWalletTransferStatus(settlementRef);
return (
<div className="rounded-lg border border-white/10 bg-[#1a1d24] p-4 text-sm space-y-2">
<p className="text-[#A0A0A0]">
Reference: <span className="font-mono text-white text-xs">{settlementRef}</span>
</p>
{loading && !status && <p className="text-[#A0A0A0]">Loading status</p>}
{status && (
<>
<p className="text-[#A0A0A0]">
Phase: <span className="text-white">{status.phase ?? status.status ?? 'pending'}</span>
</p>
{status.chainTxHash && (
<p className="text-[#A0A0A0]">
Tx: <span className="font-mono text-emerald-400 text-xs">{status.chainTxHash}</span>
</p>
)}
{status.error && <p className="text-red-400">{status.error}</p>}
</>
)}
</div>
);
}

View File

@@ -0,0 +1,90 @@
import { useCallback, useState } from 'react';
import { omnlApiHeaders, settlementApi } from '../../config/dex';
import { zChain, Z_CHAIN_ID } from '../../config/zChain';
import type { SettlementResult } from '../online-bank/useZBankSettlement';
function idem(prefix: string) {
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
}
async function postJson<T>(url: string, body: unknown): Promise<{ ok: boolean; status: number; data: T }> {
const res = await fetch(url, {
method: 'POST',
headers: omnlApiHeaders(),
body: JSON.stringify(body),
});
const data = (await res.json().catch(() => ({ error: res.statusText || `HTTP ${res.status}` }))) as T;
return { ok: res.ok, status: res.status, data };
}
export function useZWalletInternationalTransfer() {
const [loading, setLoading] = useState(false);
const [lastResult, setLastResult] = useState<SettlementResult | null>(null);
const [error, setError] = useState<string | null>(null);
const submitTransfer = useCallback(
async (params: {
amount: string;
creditorIban: string;
corridorId: string;
currency: string;
recipientAddress?: string;
}) => {
setLoading(true);
setError(null);
try {
const corridor = zChain.internationalCorridors.find((c) => c.id === params.corridorId);
const rail = corridor?.rails.includes('HYBX') ? 'HYBX' : 'SWIFT';
const payload = {
idempotencyKey: idem('Z-WALLET-INTL'),
officeId: zChain.settlement.zBridgeOfficeId,
currency: params.currency,
amount: params.amount,
creditorIban: params.creditorIban,
beneficiaryName: zChain.brand.bank,
orderingName: zChain.bankCorridor.orderingName,
creditorBic: zChain.bankCorridor.creditorBic,
debtorIban: zChain.bankCorridor.debtorIban,
remittanceInfo: `${zChain.bankCorridor.remittanceTemplate} · ${params.corridorId}`,
moneyLayers: ['M1', 'M2', 'M4'] as const,
rail,
swiftMessageKind: zChain.bankCorridor.swiftMessageKind as 'MT103',
metadata: {
sourceOfficeId: zChain.fineract.officeId,
sourceHybxWallet: zChain.fineract.hybxWalletId,
targetChainId: Z_CHAIN_ID,
integration: 'z-chain',
corridorId: params.corridorId,
},
convertToCrypto: params.recipientAddress
? {
enabled: true,
targetChainId: Z_CHAIN_ID,
tokenLineId: zChain.wallet.defaultTokenLineId,
recipientAddress: params.recipientAddress,
}
: undefined,
};
let r = await postJson<SettlementResult>(settlementApi('/z-bank/settle-m0-m4'), payload);
if (r.status === 404 || r.status === 405) {
r = await postJson<SettlementResult>(settlementApi('/external-transfer'), payload);
}
setLastResult(r.data);
if (!r.ok) {
setError((r.data as { error?: string }).error ?? `Transfer failed (${r.status})`);
}
return r;
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
setError(msg);
return { ok: false, status: 0, data: { error: msg } as SettlementResult };
} finally {
setLoading(false);
}
},
[],
);
return { submitTransfer, loading, lastResult, error, corridors: zChain.internationalCorridors };
}

View File

@@ -0,0 +1,42 @@
import { useCallback, useEffect, useState } from 'react';
import { TOKEN_AGGREGATION_URL } from '../../config/dex';
import { omnlApiHeaders } from '../../config/dex';
export type TransferStatus = {
settlementId?: string;
phase?: string;
status?: string;
error?: string;
chainTxHash?: string;
};
export function useZWalletTransferStatus(settlementRef: string | undefined) {
const [status, setStatus] = useState<TransferStatus | null>(null);
const [loading, setLoading] = useState(false);
const refresh = useCallback(async () => {
if (!settlementRef) return;
setLoading(true);
try {
const res = await fetch(`${TOKEN_AGGREGATION_URL}/z-wallet/status/${encodeURIComponent(settlementRef)}`, {
headers: omnlApiHeaders(false),
});
if (res.ok) {
setStatus((await res.json()) as TransferStatus);
}
} catch {
/* ignore */
} finally {
setLoading(false);
}
}, [settlementRef]);
useEffect(() => {
if (!settlementRef) return;
void refresh();
const t = window.setInterval(() => void refresh(), 8000);
return () => window.clearInterval(t);
}, [settlementRef, refresh]);
return { status, loading, refresh };
}

View File

@@ -27,6 +27,12 @@ const OMNL_OPS: { path: string; label: string; icon: NasaIconName }[] = [
{ path: '/omnl/terminal', label: 'Settlement terminal', icon: 'rail' },
];
const Z_ECOSYSTEM: { path: string; label: string; icon: NasaIconName }[] = [
{ path: '/z', label: 'Z Ecosystem hub', icon: 'globe' },
{ path: '/z-wallet', label: 'International Z Wallet', icon: 'globe' },
{ path: '/z-wallet#z-bot', label: 'Z Bot assistant', icon: 'rail' },
];
function LinkGrid({
title,
items,
@@ -71,6 +77,7 @@ export default function HubPage() {
<LinkGrid title="Banking & settlement" items={BANKING} />
<LinkGrid title="Chain 138 & operators" items={CHAIN138} />
</div>
<LinkGrid title="Z Ecosystem" items={Z_ECOSYSTEM} />
<LinkGrid title="OMNL operator consoles" items={OMNL_OPS} />
<section className="dash-card cb-card p-4 mt-4">
<h2 className="dash-section-title mb-3">GUOSMM official gazette</h2>

View File

@@ -0,0 +1,49 @@
import { Link } from 'react-router-dom';
import NasaIcon, { type NasaIconName } from '../components/icons/NasaIcon';
import zEcosystem from '../../../config/z-ecosystem.v1.json';
import { zChain } from '../config/zChain';
const Z_APPS: { path: string; label: string; icon: NasaIconName; description: string }[] =
zEcosystem.apps.map((app) => ({
path: app.path,
label: app.label,
icon: (app.id === 'zWallet' ? 'globe' : app.id === 'zBot' ? 'rail' : app.id === 'zOnlineBank' ? 'central-bank' : 'network') as NasaIconName,
description: app.description,
}));
export default function ZEcosystemHubPage() {
return (
<div className="dash-page cb-page max-w-[900px] mx-auto px-4 py-6">
<header className="mb-8">
<p className="text-xs font-mono text-violet-400/80 mb-1">Z Ecosystem · chain {zChain.chain.chainId}</p>
<h1 className="text-2xl font-semibold text-[#e8f4ff] flex items-center gap-2">
<NasaIcon name="globe" size={22} className="nasa-icon--telemetry" />
Z Ecosystem
</h1>
<p className="text-sm text-[#7a9bb8] mt-2 max-w-2xl">
{zChain.brand.blockchain} consumer entry international wallet, Z Bot assistant, and Z Online Bank settlement.
</p>
</header>
<section className="dash-card cb-card p-4">
<h2 className="dash-section-title mb-3">Apps</h2>
<ul className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{Z_APPS.map(({ path, label, icon, description }) => (
<li key={path}>
<Link
to={path}
className="dash-quick-links__chip flex flex-col !items-start gap-1 py-3 px-3 h-full"
>
<span className="inline-flex items-center gap-2 font-medium">
<NasaIcon name={icon} size={14} />
{label}
</span>
<span className="text-xs text-[#7a9bb8] text-left">{description}</span>
</Link>
</li>
))}
</ul>
</section>
</div>
);
}

View File

@@ -0,0 +1,126 @@
/**
* International Z Wallet — Thirdweb v5 on Z Chain (900002)
*/
import { createThirdwebClient, defineChain } from 'thirdweb';
import { ThirdwebProvider, ConnectButton, useActiveAccount, useWalletBalance } from 'thirdweb/react';
import { inAppWallet } from 'thirdweb/wallets';
import { chainRpcUrls, zChain as zChainDef, zChainId } from '../config/networks';
import { zChain, zWalletEnabled } from '../config/zChain';
import InternationalTransferPanel from '../features/z-wallet/InternationalTransferPanel';
import ZBotPanel from '../features/z-bot/ZBotPanel';
import { Link } from 'react-router-dom';
const clientId = import.meta.env.VITE_THIRDWEB_CLIENT_ID || '542981292d51ec610388ba8985f027d7';
const client = createThirdwebClient({ clientId });
const zThirdwebChain = defineChain({
id: zChainDef.id,
name: zChainDef.name,
rpc: chainRpcUrls[zChainId] || zChain.chain.rpcDefault,
nativeCurrency: {
name: zChainDef.nativeCurrency.name,
symbol: zChainDef.nativeCurrency.symbol,
decimals: zChainDef.nativeCurrency.decimals,
},
});
const wallets = [
inAppWallet({
auth: {
options: zChain.wallet.supportedAuth as ('email' | 'google' | 'apple' | 'passkey')[],
},
metadata: {
name: zChain.wallet.thirdwebMetadataName,
image: {
src: '/favicon.ico',
width: 32,
height: 32,
},
},
}),
];
function ZWalletContent() {
const account = useActiveAccount();
const { data: balance, isLoading: balanceLoading } = useWalletBalance({
client,
chain: zThirdwebChain,
address: account?.address,
});
if (!zWalletEnabled) {
return (
<div className="app-page-shell max-w-2xl mx-auto p-6 text-center text-[#A0A0A0]">
International Z Wallet is disabled. Set VITE_ENABLE_Z_WALLET=true to enable.
</div>
);
}
return (
<div className="app-page-shell max-w-2xl mx-auto p-3 sm:p-6 space-y-6 w-full min-w-0">
<div className="text-center">
<p className="text-xs font-mono text-violet-400/90 mb-1">
<Link to="/z" className="hover:underline">
Z Ecosystem
</Link>{' '}
· chain {zChainId}
</p>
<h1 className="text-2xl font-bold text-white mb-2">{zChain.brand.wallet}</h1>
<p className="text-[#A0A0A0] text-sm">
{zChain.brand.blockchain} email, Google, Apple, passkey, or external wallet. International corridors via{' '}
{zChain.brand.bank}.
</p>
</div>
<div className="flex flex-col items-center gap-6 p-6 bg-[#252830] rounded-xl border border-violet-500/25">
<ConnectButton
client={client}
chain={zThirdwebChain}
wallets={wallets}
theme="dark"
connectButton={{
label: `Connect ${zChain.brand.wallet}`,
style: {
backgroundColor: '#7c3aed',
color: 'white',
borderRadius: '0.75rem',
padding: '0.75rem 1.5rem',
},
}}
/>
{account && (
<div className="w-full space-y-2 text-sm text-center">
<p className="text-[#A0A0A0]">
Address: <span className="font-mono text-white">{account.address}</span>
</p>
{balanceLoading ? (
<p className="text-[#A0A0A0]">Loading balance</p>
) : balance ? (
<p className="text-[#A0A0A0]">
Balance:{' '}
<span className="text-white">
{balance.displayValue} {balance.symbol}
</span>
</p>
) : null}
<p className="text-xs text-[#7a9bb8]">
Office {zChain.fineract.officeId} · {zChain.fineract.hybxWalletId}
</p>
</div>
)}
</div>
<InternationalTransferPanel recipientAddress={account?.address} />
<ZBotPanel walletAddress={account?.address} chainId={zChainId} />
</div>
);
}
export default function ZWalletPage() {
return (
<ThirdwebProvider>
<ZWalletContent />
</ThirdwebProvider>
);
}

View File

@@ -37,7 +37,7 @@ module.exports = {
},
networks: {
hardhat: {
chainId: 31337,
chainId: process.env.Z_CHAIN_LOCAL === "1" ? 900002 : 31337,
},
mainnet: {
url: process.env.ETHEREUM_MAINNET_RPC || "https://eth.llamarpc.com",
@@ -94,6 +94,13 @@ module.exports = {
accounts: sharedAccounts,
chainId: 43114,
},
zchain: {
url: process.env.CHAIN_900002_RPC_URL || "http://127.0.0.1:8546",
accounts: sharedAccounts.length
? sharedAccounts
: ["0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"],
chainId: 900002,
},
},
// Single Etherscan API key = V2 for all chains (see docs/deployment/ETHERSCAN_V2_VERIFY_OTHER_CHAINS.md)
etherscan: {

39
hardhat.zchain.config.js Normal file
View File

@@ -0,0 +1,39 @@
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();
function resolveAccounts() {
const rawPrivateKey = (process.env.PRIVATE_KEY || "").trim();
if (!rawPrivateKey) return [];
const normalizedPrivateKey = rawPrivateKey.startsWith("0x") ? rawPrivateKey : `0x${rawPrivateKey}`;
if (/^0x[0-9a-fA-F]{64}$/.test(normalizedPrivateKey)) return [normalizedPrivateKey];
return [];
}
const sharedAccounts = resolveAccounts();
const devAccount = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
/** Minimal Hardhat config — Z Chain deploy only (avoids full monorepo compile). */
module.exports = {
solidity: {
compilers: [
{ version: "0.8.20", settings: { optimizer: { enabled: true, runs: 200 } } },
{ version: "0.8.19", settings: { optimizer: { enabled: true, runs: 200 } } },
],
},
networks: {
hardhat: {
chainId: process.env.Z_CHAIN_LOCAL === "1" ? 900002 : 31337,
},
zchain: {
url: process.env.CHAIN_900002_RPC_URL || "http://127.0.0.1:8546",
accounts: sharedAccounts.length ? sharedAccounts : [devAccount],
chainId: 900002,
},
},
paths: {
sources: "./contracts/z-chain",
tests: "./test/z-chain",
cache: "./cache/zchain",
artifacts: "./artifacts/zchain",
},
};

View File

@@ -42,6 +42,12 @@
"deploy:logger:gnosis": "hardhat run scripts/ccip-deployment/deploy-ccip-logger-multichain.js --network gnosis",
"deploy:logger:cronos": "hardhat run scripts/ccip-deployment/deploy-ccip-logger-multichain.js --network cronos",
"deploy:reporter:chain138": "node scripts/ccip-deployment/deploy-ccip-reporter.js",
"zchain:complete": "powershell -ExecutionPolicy Bypass -File scripts/deployment/complete-z-chain.ps1",
"zchain:deploy-portal": "powershell -ExecutionPolicy Bypass -File scripts/deployment/deploy-z-ecosystem.ps1",
"zchain:go-live": "powershell -ExecutionPolicy Bypass -File scripts/deployment/go-live-z-ecosystem.ps1",
"zchain:deploy-production": "bash scripts/deployment/deploy-z-ecosystem-production.sh",
"zchain:compile": "hardhat compile --config hardhat.zchain.config.js",
"zchain:deploy": "hardhat run scripts/deployment/deploy-z-chain-contracts.js --network zchain --config hardhat.zchain.config.js",
"verify:logger": "hardhat verify --network mainnet",
"verify:reporter": "hardhat verify --network chain138",
"keeper": "node scripts/reserve/keeper-service.js",

View File

@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Script.sol";
import "forge-std/console.sol";
import "../../contracts/z-chain/ZChainUSDC.sol";
import "../../contracts/emoney/AccountWalletRegistry.sol";
/**
* @title DeployZChainEmoney
* @notice Deploys cUSDC + AccountWalletRegistry on Z Chain (900002)
*/
contract DeployZChainEmoney is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address deployer = vm.addr(deployerPrivateKey);
string memory rpcUrl = vm.envOr("CHAIN_900002_RPC_URL", string("http://127.0.0.1:8546"));
console.log("Deployer:", deployer);
console.log("RPC URL:", rpcUrl);
console.log("Chain ID: 900002");
vm.startBroadcast(deployerPrivateKey);
ZChainUSDC cUsdc = new ZChainUSDC(deployer);
AccountWalletRegistry registry = new AccountWalletRegistry(deployer);
vm.stopBroadcast();
console.log("\n=== Z Chain deployment ===");
console.log("cUSDC:", address(cUsdc));
console.log("AccountWalletRegistry:", address(registry));
console.log("\nUpdate config/z-chain-deployed.v1.json and hybx-omnl-cross-chain-lines.json chains.900002");
}
}

View File

@@ -0,0 +1,11 @@
# Local Z Chain without Docker — Anvil (chainId 900002)
$ErrorActionPreference = "Stop"
$RpcPort = if ($env:Z_CHAIN_RPC_PORT) { $env:Z_CHAIN_RPC_PORT } else { "8546" }
if (-not (Get-Command anvil -ErrorAction SilentlyContinue)) {
Write-Error "Install Foundry (anvil): https://book.getfoundry.sh/getting-started/installation"
}
Write-Host "Starting Anvil as Z Chain on http://127.0.0.1:$RpcPort (chainId 900002)"
Write-Host "Use Anvil account #0 private key as PRIVATE_KEY for deploy."
anvil --chain-id 900002 --port $RpcPort --host 127.0.0.1

View File

@@ -0,0 +1,16 @@
#!/usr/bin/env bash
# Local Z Chain without Docker — Anvil (chainId 900002) for contract deploy / wallet dev.
set -euo pipefail
RPC_PORT="${Z_CHAIN_RPC_PORT:-8546}"
CHAIN_ID=900002
if ! command -v anvil &>/dev/null; then
echo "ERROR: install Foundry (anvil) — https://book.getfoundry.sh/getting-started/installation"
exit 1
fi
echo "Starting Anvil as Z Chain on http://127.0.0.1:$RPC_PORT (chainId $CHAIN_ID)"
echo "Account #0 private key is printed by Anvil — use as PRIVATE_KEY for deploy."
echo ""
exec anvil --chain-id "$CHAIN_ID" --port "$RPC_PORT" --host 127.0.0.1

View File

@@ -0,0 +1,66 @@
# Bootstrap local Z Chain (chainId 900002) with Hyperledger Besu via Docker.
$ErrorActionPreference = "Stop"
$Root = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).Path
$NetworkDir = if ($env:Z_CHAIN_NETWORK_DIR) { $env:Z_CHAIN_NETWORK_DIR } else { Join-Path $Root "logs\z-chain-network" }
$GenesisConfig = Join-Path $Root "config\z-chain-network-config.json"
$RpcPort = if ($env:Z_CHAIN_RPC_PORT) { $env:Z_CHAIN_RPC_PORT } else { "8546" }
$ContainerName = if ($env:Z_CHAIN_BESU_CONTAINER) { $env:Z_CHAIN_BESU_CONTAINER } else { "z-chain-besu" }
try {
docker info 2>&1 | Out-Null
if ($LASTEXITCODE -ne 0) { throw "Docker daemon not running" }
} catch {
Write-Error "Docker is installed but not running. Start Docker Desktop or run: scripts\deployment\complete-z-chain.ps1 (Hardhat fallback)"
}
if (-not (Test-Path $GenesisConfig)) {
Write-Error "Missing $GenesisConfig"
}
New-Item -ItemType Directory -Force -Path $NetworkDir | Out-Null
$GenesisOut = Join-Path $NetworkDir "genesis.json"
if (-not (Test-Path $GenesisOut)) {
Write-Host "Generating IBFT2 network files in $NetworkDir ..."
docker run --rm `
-v "${Root}\config:/config:ro" `
-v "${NetworkDir}:/network" `
hyperledger/besu:latest `
operator generate-blockchain-config `
--config-file=/config/z-chain-network-config.json `
--to=/network
}
$existing = docker ps -a --format "{{.Names}}" 2>$null | Where-Object { $_ -eq $ContainerName }
if ($existing) {
Write-Host "Removing existing container $ContainerName ..."
docker rm -f $ContainerName | Out-Null
}
$NodeKey = Join-Path $NetworkDir "keys\node1\key"
if (-not (Test-Path $NodeKey) -or -not (Test-Path $GenesisOut)) {
Write-Error "Generated network files missing under $NetworkDir"
}
Write-Host "Starting Z Chain Besu on http://127.0.0.1:$RpcPort (chainId 900002) ..."
docker run -d `
--name $ContainerName `
-p "${RpcPort}:8545" `
-v "${NetworkDir}:/network:ro" `
hyperledger/besu:latest `
--data-path=/tmp/besu `
--genesis-file=/network/genesis.json `
--node-private-key-file=/network/keys/node1/key `
--rpc-http-enabled `
--rpc-http-api=ETH,NET,WEB3,IBFT,ADMIN,DEBUG `
--rpc-http-host=0.0.0.0 `
--rpc-http-port=8545 `
--rpc-http-cors-origins="*" `
--host-allowlist="*" `
--min-gas-price=0 | Out-Null
Write-Host ""
Write-Host "Z Chain local RPC: http://127.0.0.1:$RpcPort"
Write-Host 'Set env: CHAIN_900002_RPC_URL=http://127.0.0.1:' + $RpcPort
Write-Host 'Set env: VITE_RPC_URL_900002=http://127.0.0.1:' + $RpcPort

View File

@@ -0,0 +1,70 @@
#!/usr/bin/env bash
# Bootstrap local Z Chain (chainId 900002) with Hyperledger Besu via Docker.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
NETWORK_DIR="${Z_CHAIN_NETWORK_DIR:-$ROOT/logs/z-chain-network}"
GENESIS_CONFIG="$ROOT/config/z-chain-network-config.json"
RPC_PORT="${Z_CHAIN_RPC_PORT:-8546}"
CONTAINER_NAME="${Z_CHAIN_BESU_CONTAINER:-z-chain-besu}"
cd "$ROOT"
if ! command -v docker &>/dev/null; then
echo "ERROR: docker is required. Install Docker and retry."
exit 1
fi
if [[ ! -f "$GENESIS_CONFIG" ]]; then
echo "ERROR: missing $GENESIS_CONFIG"
exit 1
fi
mkdir -p "$NETWORK_DIR"
if [[ ! -f "$NETWORK_DIR/genesis.json" ]]; then
echo "Generating IBFT2 network files in $NETWORK_DIR ..."
docker run --rm \
-v "$ROOT/config:/config:ro" \
-v "$NETWORK_DIR:/network" \
hyperledger/besu:latest \
operator generate-blockchain-config \
--config-file=/config/z-chain-network-config.json \
--to=/network
fi
if docker ps -a --format '{{.Names}}' | grep -qx "$CONTAINER_NAME"; then
echo "Removing existing container $CONTAINER_NAME ..."
docker rm -f "$CONTAINER_NAME" >/dev/null
fi
NODE_KEY="$NETWORK_DIR/keys/node1/key"
GENESIS="$NETWORK_DIR/genesis.json"
if [[ ! -f "$NODE_KEY" || ! -f "$GENESIS" ]]; then
echo "ERROR: generated network files missing under $NETWORK_DIR"
exit 1
fi
echo "Starting Z Chain Besu on http://127.0.0.1:$RPC_PORT (chainId 900002) ..."
docker run -d \
--name "$CONTAINER_NAME" \
-p "${RPC_PORT}:8545" \
-v "$NETWORK_DIR:/network:ro" \
hyperledger/besu:latest \
--data-path=/tmp/besu \
--genesis-file=/network/genesis.json \
--node-private-key-file=/network/keys/node1/key \
--rpc-http-enabled \
--rpc-http-api=ETH,NET,WEB3,IBFT,ADMIN,DEBUG \
--rpc-http-host=0.0.0.0 \
--rpc-http-port=8545 \
--rpc-http-cors-origins="*" \
--host-allowlist="*" \
--min-gas-price=0
echo ""
echo "Z Chain local RPC: http://127.0.0.1:$RPC_PORT"
echo "Export: CHAIN_900002_RPC_URL=http://127.0.0.1:$RPC_PORT"
echo "Export: VITE_RPC_URL_900002=http://127.0.0.1:$RPC_PORT"
echo "Verify: cast chain-id --rpc-url http://127.0.0.1:$RPC_PORT"

View File

@@ -0,0 +1,59 @@
#!/usr/bin/env bash
# Production Z Chain Besu bootstrap (chainId 900002) on a banking LXC or VM.
# Generates IBFT2 keys once, then runs Besu with persistent data under /opt/besu-z-chain.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
NETWORK_DIR="${Z_CHAIN_NETWORK_DIR:-/opt/besu-z-chain/network}"
DATA_PATH="${Z_CHAIN_DATA_PATH:-/opt/besu-z-chain/data}"
GENESIS_CONFIG="$ROOT/config/z-chain-network-config.json"
RPC_PORT="${Z_CHAIN_RPC_PORT:-8546}"
CONTAINER_NAME="${Z_CHAIN_BESU_CONTAINER:-z-chain-besu-prod}"
if [[ $EUID -ne 0 ]]; then
echo "Run as root on the target host (or use sudo)."
exit 1
fi
if ! command -v docker &>/dev/null; then
echo "ERROR: docker required on production host"
exit 1
fi
mkdir -p "$NETWORK_DIR" "$DATA_PATH"
if [[ ! -f "$NETWORK_DIR/genesis.json" ]]; then
echo "Generating IBFT2 network files in $NETWORK_DIR ..."
docker run --rm \
-v "$ROOT/config:/config:ro" \
-v "$NETWORK_DIR:/network" \
hyperledger/besu:latest \
operator generate-blockchain-config \
--config-file=/config/z-chain-network-config.json \
--to=/network
fi
docker rm -f "$CONTAINER_NAME" 2>/dev/null || true
echo "Starting production Z Chain Besu on 0.0.0.0:$RPC_PORT ..."
docker run -d \
--name "$CONTAINER_NAME" \
--restart unless-stopped \
-p "127.0.0.1:${RPC_PORT}:8545" \
-v "$NETWORK_DIR:/network:ro" \
-v "$DATA_PATH:/data" \
hyperledger/besu:latest \
--data-path=/data \
--genesis-file=/network/genesis.json \
--node-private-key-file=/network/keys/node1/key \
--rpc-http-enabled \
--rpc-http-api=ETH,NET,WEB3,IBFT,ADMIN \
--rpc-http-host=127.0.0.1 \
--rpc-http-port=8545 \
--host-allowlist="*" \
--min-gas-price=0
echo ""
echo "Production Z Chain RPC (localhost): http://127.0.0.1:$RPC_PORT"
echo "Expose via nginx/Cloudflare as https://rpc.z.omdnl.org"
echo "Set CHAIN_900002_RPC_URL on portal LXC after tunnel/nginx is wired."

View File

@@ -0,0 +1,90 @@
# Complete Z Chain local setup: RPC node + contract deploy + config sync.
# Uses Docker Besu when available; otherwise Hardhat node chainId 900002.
$ErrorActionPreference = "Stop"
$Root = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).Path
$RpcPort = if ($env:Z_CHAIN_RPC_PORT) { $env:Z_CHAIN_RPC_PORT } else { "8546" }
$RpcUrl = "http://127.0.0.1:$RpcPort"
$NodeLog = Join-Path $Root "logs\z-chain-node.log"
$ErrLog = Join-Path $Root "logs\z-chain-node.err.log"
$PidFile = Join-Path $Root "logs\z-chain-node.pid"
Set-Location $Root
New-Item -ItemType Directory -Force -Path (Join-Path $Root "logs") | Out-Null
function Test-RpcReady {
try {
$body = '{"jsonrpc":"2.0","id":1,"method":"eth_chainId","params":[]}'
$r = Invoke-RestMethod -Uri $RpcUrl -Method Post -Body $body -ContentType "application/json" -TimeoutSec 2
return ($r.result -eq "0xdbba2")
} catch {
return $false
}
}
function Stop-ZChainNode {
if (Test-Path $PidFile) {
$oldPid = Get-Content $PidFile -ErrorAction SilentlyContinue
if ($oldPid) {
Stop-Process -Id $oldPid -Force -ErrorAction SilentlyContinue
}
Remove-Item $PidFile -Force -ErrorAction SilentlyContinue
}
try {
docker rm -f z-chain-besu 2>$null | Out-Null
} catch {}
$conn = Get-NetTCPConnection -LocalPort $RpcPort -ErrorAction SilentlyContinue | Select-Object -First 1
if ($conn -and $conn.OwningProcess) {
Stop-Process -Id $conn.OwningProcess -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 2
}
}
$dockerOk = $false
try {
docker info 2>&1 | Out-Null
if ($LASTEXITCODE -eq 0) { $dockerOk = $true }
} catch {}
Stop-ZChainNode
if ($dockerOk) {
Write-Host "Starting Z Chain via Docker Besu ..."
& "$PSScriptRoot\bootstrap-z-chain-local.ps1"
} else {
$env:Z_CHAIN_LOCAL = "1"
$hardhat = Join-Path $Root "node_modules\.bin\hardhat.cmd"
$nodeCmd = "set Z_CHAIN_LOCAL=1&& `"$hardhat`" node --port $RpcPort --hostname 127.0.0.1"
$nodeProc = Start-Process -FilePath "cmd.exe" -ArgumentList @("/c", $nodeCmd) -WorkingDirectory $Root -PassThru -WindowStyle Hidden -RedirectStandardOutput $NodeLog -RedirectStandardError $ErrLog
$nodeProc.Id | Set-Content $PidFile
Start-Sleep -Seconds 8
}
$deadline = (Get-Date).AddSeconds(90)
while (-not (Test-RpcReady)) {
if ((Get-Date) -gt $deadline) {
Write-Error "Z Chain RPC not ready at $RpcUrl after 90s. See $NodeLog and $ErrLog"
}
Start-Sleep -Seconds 2
}
Write-Host "Z Chain RPC ready at $RpcUrl"
$env:CHAIN_900002_RPC_URL = $RpcUrl
$env:Z_CHAIN_LOCAL = "1"
if (-not $env:PRIVATE_KEY) {
$env:PRIVATE_KEY = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
}
Write-Host "Compiling Z Chain contracts ..."
npx hardhat compile --config hardhat.zchain.config.js 2>&1 | Out-Null
if ($LASTEXITCODE -ne 0) { throw "zchain compile failed" }
Write-Host "Deploying Z Chain contracts ..."
npx hardhat run scripts/deployment/deploy-z-chain-contracts.js --network zchain --config hardhat.zchain.config.js
if ($LASTEXITCODE -ne 0) { throw "contract deploy failed" }
Write-Host ""
Write-Host "Z Chain setup complete."
Write-Host " RPC: $RpcUrl"
Write-Host " Set VITE_ENABLE_Z_WALLET=true and VITE_RPC_URL_900002=$RpcUrl"
Write-Host " Updated config/z-chain-deployed.v1.json"

View File

@@ -98,6 +98,9 @@ cd "$REPO_DIR/frontend-dapp"
export VITE_TOKEN_AGGREGATION_URL="${VITE_TOKEN_AGGREGATION_URL:-/api/v1}"
export VITE_SETTLEMENT_MIDDLEWARE_URL="${VITE_SETTLEMENT_MIDDLEWARE_URL:-/settlement}"
export VITE_DBIS_EXCHANGE_URL="${VITE_DBIS_EXCHANGE_URL:-/exchange}"
export VITE_ENABLE_Z_WALLET="${VITE_ENABLE_Z_WALLET:-true}"
export VITE_RPC_URL_900002="${VITE_RPC_URL_900002:-${CHAIN_900002_RPC_URL:-https://rpc.z.omdnl.org}}"
export VITE_Z_CHAIN_EXPLORER_URL="${VITE_Z_CHAIN_EXPLORER_URL:-https://explorer.z.omdnl.org}"
# Never embed operator API keys in production frontend — nginx injects server-side.
unset VITE_OMNL_API_KEY
if command -v pnpm >/dev/null 2>&1; then
@@ -147,6 +150,11 @@ start_svc() {
SWIFT_LISTENER_URL="${SWIFT_LISTENER_URL:-http://127.0.0.1:8788}" \
RPC_URL_138="${RPC_URL_138:-${CHAIN_138_RPC_URL:-}}" \
CHAIN_138_RPC_URL="${CHAIN_138_RPC_URL:-${RPC_URL_138:-}}" \
CHAIN_900002_RPC_URL="${CHAIN_900002_RPC_URL:-}" \
AZURE_OPENAI_ENDPOINT="${AZURE_OPENAI_ENDPOINT:-}" \
AZURE_OPENAI_API_KEY="${AZURE_OPENAI_API_KEY:-}" \
AZURE_OPENAI_DEPLOYMENT="${AZURE_OPENAI_DEPLOYMENT:-}" \
AZURE_OPENAI_API_VERSION="${AZURE_OPENAI_API_VERSION:-2024-02-15-preview}" \
OMNL_MINT_OPERATOR_PRIVATE_KEY="${OMNL_MINT_OPERATOR_PRIVATE_KEY:-}" \
bash -c "$cmd" >"$LOG_DIR/$name.log" 2>&1 &
echo $! >"$LOG_DIR/$name.pid"
@@ -179,6 +187,10 @@ echo
curl -sf "http://127.0.0.1:3012/api/v1/exchange/health" | head -c 200 || true
echo
curl -sf -o /dev/null -w "frontend=%{http_code}\n" "http://127.0.0.1:$FRONTEND_PORT/central-bank"
curl -sf -o /dev/null -w "z-hub=%{http_code}\n" "http://127.0.0.1:$FRONTEND_PORT/z"
curl -sf -o /dev/null -w "z-wallet=%{http_code}\n" "http://127.0.0.1:$FRONTEND_PORT/z-wallet"
curl -sf "http://127.0.0.1:3000/api/v1/z-bot/health" | head -c 200 || true
echo
curl -sf "http://127.0.0.1:8788/health" 2>/dev/null | head -c 80 || log "WARN: SWIFT listener :8788 not reachable"
if [[ -n "${DATABASE_URL:-}" ]] && [[ "${SEED_SUPER_ADMIN_USERS:-1}" == "1" ]]; then

View File

@@ -41,6 +41,7 @@ check_host exchange.omdnl.org /trade
check_host secure.d-bis.org /central-bank
check_host office24.omdnl.org /office-24
check_host digital.omdnl.org /swap
check_host digital.omdnl.org /z
log "OMNL domain deploy complete"
log "DNS: point all domains A record to 76.53.10.34 (or Cloudflare proxy to origin)"

View File

@@ -0,0 +1,74 @@
const { ethers } = require('hardhat');
const fs = require('fs');
const path = require('path');
require('dotenv').config();
const ROOT = path.resolve(__dirname, '../..');
function syncAddresses(cUsdc, registry) {
const today = new Date().toISOString().slice(0, 10);
const deployedPath = path.join(ROOT, 'config/z-chain-deployed.v1.json');
const deployed = JSON.parse(fs.readFileSync(deployedPath, 'utf8'));
deployed.updated = today;
deployed.status = 'active';
deployed.contracts.cUSDC = cUsdc;
deployed.contracts.accountWalletRegistry = registry;
fs.writeFileSync(deployedPath, `${JSON.stringify(deployed, null, 2)}\n`);
const integrationPath = path.join(ROOT, 'config/z-chain-integration.v1.json');
const integration = JSON.parse(fs.readFileSync(integrationPath, 'utf8'));
integration.contracts.cUSDC = cUsdc;
integration.contracts.accountWalletRegistry = registry;
fs.writeFileSync(integrationPath, `${JSON.stringify(integration, null, 2)}\n`);
const hybxPath = path.join(ROOT, 'config/hybx-omnl-cross-chain-lines.json');
const hybx = JSON.parse(fs.readFileSync(hybxPath, 'utf8'));
for (const line of hybx.lines || []) {
if (line.chains?.['900002']) {
line.chains['900002'].tokenM1 = cUsdc;
}
}
fs.writeFileSync(hybxPath, `${JSON.stringify(hybx, null, 2)}\n`);
const broadcastDir = path.join(ROOT, 'broadcast/DeployZChainEmoney.s.sol/900002');
fs.mkdirSync(broadcastDir, { recursive: true });
const broadcast = {
transactions: [
{ contractName: 'ZChainUSDC', contractAddress: cUsdc },
{ contractName: 'AccountWalletRegistryZ', contractAddress: registry },
],
};
fs.writeFileSync(path.join(broadcastDir, 'run-latest.json'), `${JSON.stringify(broadcast, null, 2)}\n`);
console.log('\nConfig updated:');
console.log(' cUSDC:', cUsdc);
console.log(' AccountWalletRegistry:', registry);
}
async function main() {
const [deployer] = await ethers.getSigners();
const network = await ethers.provider.getNetwork();
console.log('Deployer:', deployer.address);
console.log('Chain ID:', network.chainId.toString());
console.log('Balance:', ethers.formatEther(await ethers.provider.getBalance(deployer.address)));
const ZChainUSDC = await ethers.getContractFactory('ZChainUSDC');
const cUsdc = await ZChainUSDC.deploy(deployer.address);
await cUsdc.waitForDeployment();
const cUsdcAddress = await cUsdc.getAddress();
console.log('ZChainUSDC deployed:', cUsdcAddress);
const AccountWalletRegistry = await ethers.getContractFactory('AccountWalletRegistryZ');
const registry = await AccountWalletRegistry.deploy(deployer.address);
await registry.waitForDeployment();
const registryAddress = await registry.getAddress();
console.log('AccountWalletRegistry deployed:', registryAddress);
syncAddresses(cUsdcAddress, registryAddress);
}
main().catch((err) => {
console.error(err);
process.exit(1);
});

View File

@@ -0,0 +1,23 @@
#!/usr/bin/env bash
# Deploy Z Chain contracts (cUSDC + AccountWalletRegistry) on chainId 900002.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
cd "$ROOT"
RPC="${CHAIN_900002_RPC_URL:-http://127.0.0.1:8546}"
if [[ -z "${PRIVATE_KEY:-}" ]]; then
echo "ERROR: set PRIVATE_KEY (Besu dev key #1 works for local bootstrap)"
exit 1
fi
echo "Deploying to Z Chain at $RPC ..."
forge script script/z-chain/DeployZChainEmoney.s.sol:DeployZChainEmoney \
--rpc-url "$RPC" \
--chain-id 900002 \
--broadcast \
--profile zchain
echo "Done. Copy deployed addresses into config/z-chain-deployed.v1.json"
echo "Or run: node scripts/deployment/sync-z-chain-deployed-addresses.mjs"

View File

@@ -0,0 +1,52 @@
#!/usr/bin/env bash
# Production deploy: Z Chain env + OMNL banking portal (LXC / server).
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
ENV_FILE="${Z_ECOSYSTEM_ENV:-${OMNL_BANK_ENV:-$ROOT/.env}}"
PROD_CFG="$ROOT/config/z-chain-production.v1.json"
log() { echo "[$(date -Iseconds)] $*"; }
if [[ -f "$ENV_FILE" ]]; then
set -a
set +u
# shellcheck disable=SC1090
source "$ENV_FILE"
set -u
set +a
fi
export CHAIN_900002_RPC_URL="${CHAIN_900002_RPC_URL:-https://rpc.z.omdnl.org}"
export VITE_RPC_URL_900002="${VITE_RPC_URL_900002:-$CHAIN_900002_RPC_URL}"
export VITE_Z_CHAIN_EXPLORER_URL="${VITE_Z_CHAIN_EXPLORER_URL:-https://explorer.z.omdnl.org}"
export VITE_ENABLE_Z_WALLET="${VITE_ENABLE_Z_WALLET:-true}"
log "Z Ecosystem production deploy"
log " config: $PROD_CFG"
log " rpc: $CHAIN_900002_RPC_URL"
if [[ "${Z_CHAIN_BOOTSTRAP_PROD:-0}" == "1" ]]; then
log "Bootstrapping Besu Z Chain on this host..."
sudo bash "$ROOT/scripts/deployment/bootstrap-z-chain-production.sh"
fi
if [[ "${Z_CHAIN_DEPLOY_CONTRACTS:-0}" == "1" ]]; then
log "Deploying Z Chain contracts..."
export CHAIN_900002_RPC_URL
node "$ROOT/scripts/deployment/deploy-z-chain-contracts.js" --network zchain
node "$ROOT/scripts/deployment/sync-z-chain-deployed-addresses.mjs"
fi
log "Building and starting OMNL stack (includes Z Wallet routes)..."
bash "$ROOT/scripts/deployment/deploy-omnl-bank-production.sh"
if [[ "${Z_ECOSYSTEM_PUSH_PCT:-0}" == "1" ]]; then
log "Pushing portal bundle to Proxmox LXC..."
bash "$ROOT/scripts/deployment/deploy-omnl-banking-portal-pct.sh"
fi
log "Done. Verify:"
log " curl -sf http://127.0.0.1:3011/api/v1/settlement/health"
log " curl -sf http://127.0.0.1:3000/api/v1/z-bot/health"
log " Portal: https://digital.omdnl.org/z-wallet (after nginx/Cloudflare)"

View File

@@ -0,0 +1,133 @@
# Deploy Z Ecosystem portal locally (chain + settlement + API + portal)
$ErrorActionPreference = "Stop"
$Root = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).Path
$LogDir = Join-Path $Root "logs"
New-Item -ItemType Directory -Force -Path $LogDir | Out-Null
function Import-DotEnvFile([string]$Path) {
if (-not (Test-Path $Path)) { return }
Get-Content $Path | ForEach-Object {
$line = $_.Trim()
if ($line -eq "" -or $line.StartsWith("#")) { return }
if ($line -match '^\s*([^=]+)=(.*)$') {
$name = $matches[1].Trim()
$value = $matches[2].Trim().Trim('"').Trim("'")
Set-Item -Path "env:$name" -Value $value
}
}
}
function Test-ZChainRpc {
try {
$body = '{"jsonrpc":"2.0","id":1,"method":"eth_chainId","params":[]}'
$r = Invoke-RestMethod -Uri "http://127.0.0.1:8546" -Method Post -Body $body -ContentType "application/json" -TimeoutSec 2
return ($r.result -eq "0xdbba2")
} catch { return $false }
}
function Stop-Port($port) {
$conn = Get-NetTCPConnection -LocalPort $port -ErrorAction SilentlyContinue | Select-Object -First 1
if ($conn -and $conn.OwningProcess) {
Stop-Process -Id $conn.OwningProcess -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 1
}
}
function Start-BackgroundService([string]$Name, [string]$WorkDir, [string]$Command, [string]$LogFile) {
Write-Host "Starting $Name ..."
Start-Process -FilePath "cmd.exe" -ArgumentList @("/c", "$Command > `"$LogFile`" 2>&1") `
-WorkingDirectory $WorkDir -WindowStyle Hidden
}
Set-Location $Root
Import-DotEnvFile (Join-Path $Root ".env")
Import-DotEnvFile (Join-Path $Root "config\z-ecosystem.local.env")
$env:CHAIN_900002_RPC_URL = if ($env:CHAIN_900002_RPC_URL) { $env:CHAIN_900002_RPC_URL } else { "http://127.0.0.1:8546" }
$env:SETTLEMENT_MIDDLEWARE_URL = if ($env:SETTLEMENT_MIDDLEWARE_URL) { $env:SETTLEMENT_MIDDLEWARE_URL } else { "http://127.0.0.1:3011" }
$env:SETTLEMENT_URL = $env:SETTLEMENT_MIDDLEWARE_URL
$env:TOKEN_AGGREGATION_URL = "http://127.0.0.1:3000"
if (-not $env:SETTLEMENT_MIDDLEWARE_CONFIG) {
$env:SETTLEMENT_MIDDLEWARE_CONFIG = Join-Path $Root "config\settlement-middleware.production.v1.json"
}
if (-not $env:OMNL_SUPPORTED_CHAINS_CONFIG) {
$env:OMNL_SUPPORTED_CHAINS_CONFIG = Join-Path $Root "config\omnl-supported-chains.v1.json"
}
if (-not (Test-ZChainRpc)) {
Write-Host "Z Chain RPC not running - starting complete-z-chain ..."
& "$PSScriptRoot\complete-z-chain.ps1"
}
Write-Host "Building packages (settlement-core) ..."
foreach ($pkg in @("packages\integration-foundation", "packages\settlement-core")) {
$pkgDir = Join-Path $Root $pkg
if (Test-Path $pkgDir) {
Set-Location $pkgDir
if (-not (Test-Path node_modules)) { npm install --legacy-peer-deps 2>&1 | Out-Null }
npm run build
if ($LASTEXITCODE -ne 0) { throw "$pkg build failed" }
}
}
Write-Host "Building settlement-middleware ..."
Set-Location "$Root\services\settlement-middleware"
if (-not (Test-Path node_modules)) { npm install --legacy-peer-deps 2>&1 | Out-Null }
npm run build
if ($LASTEXITCODE -ne 0) { throw "settlement-middleware build failed" }
Write-Host "Building token-aggregation ..."
Set-Location "$Root\services\token-aggregation"
if (-not (Test-Path node_modules)) { npm install --legacy-peer-deps 2>&1 | Out-Null }
npm run build
if ($LASTEXITCODE -ne 0) { throw "token-aggregation build failed" }
Write-Host "Building frontend-dapp (Z Wallet routes) ..."
Set-Location "$Root\frontend-dapp"
if (-not (Test-Path node_modules)) { npm install --legacy-peer-deps 2>&1 | Out-Null }
npm run build
if ($LASTEXITCODE -ne 0) { throw "frontend build failed" }
Stop-Port 3011
Stop-Port 3000
Stop-Port 3002
$settlementLog = Join-Path $LogDir "settlement-middleware.log"
$tokenLog = Join-Path $LogDir "token-aggregation.log"
$portalLog = Join-Path $LogDir "portal-serve.log"
Start-BackgroundService "settlement-middleware on :3011" "$Root\services\settlement-middleware" "npm start" $settlementLog
Start-Sleep -Seconds 3
Start-BackgroundService "token-aggregation on :3000" "$Root\services\token-aggregation" "npm start" $tokenLog
Start-Sleep -Seconds 4
$env:SETTLEMENT_URL = "http://127.0.0.1:3011"
Start-BackgroundService "portal on :3002" "$Root\frontend-dapp" "npm run serve:portal" $portalLog
Start-Sleep -Seconds 5
$llmStatus = "rule-based"
try {
$health = Invoke-RestMethod -Uri "http://localhost:3000/api/v1/z-bot/health" -TimeoutSec 5
$llmStatus = $health.llm.provider
} catch { }
$settlementStatus = "unknown"
try {
$settlementStatus = (Invoke-RestMethod -Uri "http://localhost:3011/api/v1/settlement/health" -TimeoutSec 5).status
} catch { $settlementStatus = "unreachable" }
Write-Host ""
Write-Host "Z Ecosystem deployed locally:"
Write-Host " Hub: http://localhost:3002/z"
Write-Host " Wallet: http://localhost:3002/z-wallet"
Write-Host " Z Bot API: http://localhost:3000/api/v1/z-bot/health ($llmStatus)"
Write-Host " Settlement: http://localhost:3011/api/v1/settlement/health ($settlementStatus)"
Write-Host " Z Chain RPC: $($env:CHAIN_900002_RPC_URL)"
Write-Host " Logs: $LogDir"
if ($llmStatus -eq "rule-based") {
Write-Host ""
Write-Host "Azure OpenAI: copy config/z-ecosystem.local.env.example to config/z-ecosystem.local.env and set AZURE_OPENAI_* then redeploy."
}

View File

@@ -0,0 +1,136 @@
# Go live — Z Ecosystem (production build + tunnel + deploy bundle)
$ErrorActionPreference = "Stop"
$Root = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).Path
$LogDir = Join-Path $Root "logs"
$BundleDir = Join-Path $Root "dist\go-live"
$BundleTar = Join-Path $BundleDir "z-ecosystem-portal.tar.gz"
New-Item -ItemType Directory -Force -Path $LogDir, $BundleDir | Out-Null
function Import-DotEnvFile([string]$Path) {
if (-not (Test-Path $Path)) { return }
Get-Content $Path | ForEach-Object {
$line = $_.Trim()
if ($line -eq "" -or $line.StartsWith("#")) { return }
if ($line -match '^\s*([^=]+)=(.*)$') {
Set-Item -Path "env:$($matches[1].Trim())" -Value $matches[2].Trim().Trim('"').Trim("'")
}
}
}
function Stop-Port($port) {
$conn = Get-NetTCPConnection -LocalPort $port -State Listen -ErrorAction SilentlyContinue | Select-Object -First 1
if ($conn -and $conn.OwningProcess) {
Stop-Process -Id $conn.OwningProcess -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 1
}
}
Set-Location $Root
Import-DotEnvFile (Join-Path $Root ".env")
Import-DotEnvFile (Join-Path $Root "config\z-ecosystem.local.env")
# Production frontend env (same-origin paths on digital.omdnl.org)
$env:VITE_TOKEN_AGGREGATION_URL = "/api/v1"
$env:VITE_SETTLEMENT_MIDDLEWARE_URL = "/settlement"
$env:VITE_DBIS_EXCHANGE_URL = "/exchange"
$env:VITE_ENABLE_Z_WALLET = "true"
$env:VITE_RPC_URL_900002 = if ($env:VITE_RPC_URL_900002) { $env:VITE_RPC_URL_900002 } else { "https://rpc.z.omdnl.org" }
$env:VITE_Z_CHAIN_EXPLORER_URL = if ($env:VITE_Z_CHAIN_EXPLORER_URL) { $env:VITE_Z_CHAIN_EXPLORER_URL } else { "https://explorer.z.omdnl.org" }
Remove-Item Env:VITE_OMNL_API_KEY -ErrorAction SilentlyContinue
Write-Host "=== 1/4 Local stack (Z Chain + APIs + portal) ==="
& "$PSScriptRoot\deploy-z-ecosystem.ps1"
Write-Host ""
Write-Host "=== 2/4 Production frontend build ==="
Set-Location "$Root\frontend-dapp"
npm run build
if ($LASTEXITCODE -ne 0) { throw "production frontend build failed" }
Write-Host ""
Write-Host "=== 3/4 Stage deploy bundle ==="
$stageRoot = Join-Path $BundleDir "stage"
if (Test-Path $stageRoot) { Remove-Item -Recurse -Force $stageRoot }
New-Item -ItemType Directory -Force -Path $stageRoot | Out-Null
$copyPaths = @(
"config",
"frontend-dapp\dist",
"packages\integration-foundation\package.json",
"packages\integration-foundation\dist",
"packages\settlement-core\package.json",
"packages\settlement-core\dist",
"services\token-aggregation\dist",
"services\token-aggregation\package.json",
"services\token-aggregation\package-lock.json",
"services\settlement-middleware\dist",
"services\settlement-middleware\package.json",
"services\settlement-middleware\package-lock.json",
"scripts\deployment\start-omnl-banking-portal.sh",
"scripts\deployment\deploy-z-ecosystem-production.sh",
"deploy\nginx\omnl-lxc-portal.conf",
"deploy\nginx\omnl-portal-auth-current.conf.inc"
)
foreach ($rel in $copyPaths) {
$src = Join-Path $Root $rel
if (-not (Test-Path $src)) {
Write-Host " skip missing: $rel"
continue
}
$dst = Join-Path $stageRoot $rel
New-Item -ItemType Directory -Force -Path (Split-Path $dst -Parent) | Out-Null
Copy-Item -Path $src -Destination $dst -Recurse -Force
}
if (Get-Command tar -ErrorAction SilentlyContinue) {
if (Test-Path $BundleTar) { Remove-Item -Force $BundleTar }
tar -czf $BundleTar -C $stageRoot .
Write-Host " bundle: $BundleTar"
} else {
Write-Host " tar not found - bundle folder: $stageRoot"
}
Write-Host ""
Write-Host "=== 4/4 Public tunnel (cloudflared) ==="
Stop-Port 4040
$tunnelLog = Join-Path $LogDir "cloudflared-z-ecosystem.log"
$cf = "C:\Program Files (x86)\cloudflared\cloudflared.exe"
if (-not (Test-Path $cf)) { $cf = "cloudflared" }
$tunnelErr = Join-Path $LogDir "cloudflared-z-ecosystem.err.log"
Start-Process -FilePath "cmd.exe" -ArgumentList @("/c", "`"$cf`" tunnel --url http://localhost:3002 --metrics localhost:4040 > `"$tunnelLog`" 2> `"$tunnelErr`"") `
-WindowStyle Hidden
$tunnelUrl = $null
for ($i = 0; $i -lt 30; $i++) {
Start-Sleep -Seconds 1
if (Test-Path $tunnelLog) {
$log = Get-Content $tunnelLog -Raw -ErrorAction SilentlyContinue
if ($log -match '(https://[a-z0-9-]+\.trycloudflare\.com)') {
$tunnelUrl = $matches[1]
break
}
}
}
Write-Host ""
Write-Host "Z Ecosystem GO LIVE"
Write-Host "==================="
if ($tunnelUrl) {
Write-Host " Public tunnel: $tunnelUrl/z"
Write-Host " Z Wallet: $tunnelUrl/z-wallet"
Write-Host " Z Bot health: $tunnelUrl/api/v1/z-bot/health"
} else {
Write-Host " Tunnel starting - see $tunnelLog"
Write-Host " Local: http://localhost:3002/z"
}
Write-Host ""
Write-Host "Production origin digital.omdnl.org - run on dev-bis-ali:"
Write-Host ' cd ~/smom-dbis-138; git pull; bash scripts/deployment/deploy-z-ecosystem-production.sh'
Write-Host ' # or push bundle to LXC:'
Write-Host ' Z_ECOSYSTEM_PUSH_PCT=1 bash scripts/deployment/deploy-z-ecosystem-production.sh'
Write-Host ""
Write-Host "DNS still required for Z Chain RPC:"
Write-Host ' rpc.z.omdnl.org -> Besu host port 8546'
Write-Host ' explorer.z.omdnl.org -> explorer optional'

View File

@@ -0,0 +1,69 @@
#!/usr/bin/env node
/**
* Sync forge broadcast output into z-chain-deployed.v1.json and hybx cross-chain lines.
* Usage: node scripts/deployment/sync-z-chain-deployed-addresses.mjs [broadcast-run.json]
*/
import { readFileSync, writeFileSync, existsSync } from 'fs';
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dirname, '../..');
const broadcastPath =
process.argv[2] ||
resolve(root, 'broadcast/DeployZChainEmoney.s.sol/900002/run-latest.json');
if (!existsSync(broadcastPath)) {
console.error(`Broadcast file not found: ${broadcastPath}`);
console.error('Deploy first: bash scripts/deployment/deploy-z-chain-contracts.sh');
process.exit(1);
}
const broadcast = JSON.parse(readFileSync(broadcastPath, 'utf8'));
const txs = broadcast.transactions || [];
let cUsdc = null;
let registry = null;
for (const tx of txs) {
const name = tx.contractName;
const addr = tx.contractAddress;
if (!addr) continue;
if (name === 'ZChainUSDC') cUsdc = addr;
if (name === 'AccountWalletRegistry' || name === 'AccountWalletRegistryZ') registry = addr;
}
if (!cUsdc || !registry) {
console.error('Could not find ZChainUSDC and AccountWalletRegistry in broadcast');
process.exit(1);
}
const deployedPath = resolve(root, 'config/z-chain-deployed.v1.json');
const deployed = JSON.parse(readFileSync(deployedPath, 'utf8'));
deployed.updated = new Date().toISOString().slice(0, 10);
deployed.status = 'active';
deployed.contracts.cUSDC = cUsdc;
deployed.contracts.accountWalletRegistry = registry;
writeFileSync(deployedPath, `${JSON.stringify(deployed, null, 2)}\n`);
const integrationPath = resolve(root, 'config/z-chain-integration.v1.json');
const integration = JSON.parse(readFileSync(integrationPath, 'utf8'));
integration.contracts.cUSDC = cUsdc;
integration.contracts.accountWalletRegistry = registry;
writeFileSync(integrationPath, `${JSON.stringify(integration, null, 2)}\n`);
const hybxPath = resolve(root, 'config/hybx-omnl-cross-chain-lines.json');
const hybx = JSON.parse(readFileSync(hybxPath, 'utf8'));
for (const line of hybx.lines || []) {
if (line.chains?.['900002']) {
line.chains['900002'].tokenM1 = cUsdc;
}
}
writeFileSync(hybxPath, `${JSON.stringify(hybx, null, 2)}\n`);
console.log('Updated:');
console.log(' cUSDC:', cUsdc);
console.log(' AccountWalletRegistry:', registry);
console.log(' config/z-chain-deployed.v1.json');
console.log(' config/z-chain-integration.v1.json');
console.log(' config/hybx-omnl-cross-chain-lines.json (chains.900002.tokenM1)');

View File

@@ -0,0 +1,35 @@
import { Router, type Request, type Response } from 'express';
import { zBotReply, zBotLlmStatus } from '../../services/z-bot-context';
const router = Router();
router.get('/z-bot/health', (_req: Request, res: Response) => {
const llm = zBotLlmStatus();
res.json({
service: 'z-bot',
status: 'ok',
llm,
fallback: 'rule-based when Azure OpenAI is not configured',
});
});
router.post('/z-bot/chat', async (req: Request, res: Response) => {
const { message, walletAddress, chainId } = req.body as {
message?: string;
sessionId?: string;
walletAddress?: string;
chainId?: number;
};
if (!message?.trim()) {
res.status(400).json({ error: 'message is required' });
return;
}
try {
const reply = await zBotReply(message.trim(), walletAddress, chainId);
res.json({ reply, sessionId: req.body.sessionId, llm: zBotLlmStatus().provider });
} catch (e) {
res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
}
});
export default router;

View File

@@ -0,0 +1,43 @@
import { Router, type Request, type Response } from 'express';
import { loadZChainIntegration, fetchSettlementStatus } from '../../services/z-bot-context';
const router = Router();
router.get('/z-wallet/corridors', (_req: Request, res: Response) => {
try {
const integration = loadZChainIntegration() as {
internationalCorridors?: unknown[];
brand?: unknown;
chain?: unknown;
};
res.json({
brand: integration.brand,
chain: integration.chain,
corridors: integration.internationalCorridors ?? [],
});
} catch (e) {
res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
}
});
router.get('/z-wallet/status/:ref', async (req: Request, res: Response) => {
const ref = String(req.params.ref);
if (!ref) {
res.status(400).json({ error: 'Missing settlement reference' });
return;
}
const rec = await fetchSettlementStatus(ref);
if (!rec) {
res.status(404).json({ error: 'Settlement not found', settlementId: ref });
return;
}
res.json({
settlementId: rec.settlementId ?? ref,
phase: rec.phase,
status: rec.phase ?? rec.status,
chainTxHash: rec.chainTxHash,
error: rec.error,
});
});
export default router;

View File

@@ -26,6 +26,8 @@ import omnlIpsasRoutes from './routes/omnl-ipsas';
import omnlPayoutJournalRoutes from './routes/omnl-payout-journal-routes';
import omnlComplianceRoutes from './routes/omnl-compliance-routes';
import omnlTerminalRoutes from './routes/omnl-terminal-routes';
import zWalletRoutes from './routes/z-wallet';
import zBotRoutes from './routes/z-bot';
import checkpointRoutes from './routes/checkpoint';
import reserveCapacityRoutes from './routes/reserve-capacity';
import hoLiquidityRoutes from './routes/ho-liquidity-routes';
@@ -305,6 +307,8 @@ export class ApiServer {
this.app.use('/api/v1', omnlRoutes);
this.app.use('/api/v1', omnlIpsasRoutes);
this.app.use('/api/v1', omnlPayoutJournalRoutes);
this.app.use('/api/v1', zWalletRoutes);
this.app.use('/api/v1', zBotRoutes);
this.app.use('/api/v2', plannerV2Routes);
// Admin routes (stricter rate limit)

View File

@@ -0,0 +1,200 @@
import { existsSync, readFileSync } from 'fs';
import { resolve } from 'path';
function repoRoot(): string {
return resolve(__dirname, '../../../../..');
}
export function loadZChainIntegration(): Record<string, unknown> {
const p = resolve(repoRoot(), 'config/z-chain-integration.v1.json');
if (!existsSync(p)) {
throw new Error('config/z-chain-integration.v1.json not found');
}
return JSON.parse(readFileSync(p, 'utf8')) as Record<string, unknown>;
}
export function loadZEcosystem(): Record<string, unknown> {
const p = resolve(repoRoot(), 'config/z-ecosystem.v1.json');
if (!existsSync(p)) {
throw new Error('config/z-ecosystem.v1.json not found');
}
return JSON.parse(readFileSync(p, 'utf8')) as Record<string, unknown>;
}
function rpcUrl(chainId: number): string | undefined {
if (chainId === 138) return process.env.RPC_URL_138 || process.env.RPC_URL;
return process.env[`CHAIN_${chainId}_RPC_URL`];
}
export async function fetchWalletBalance(
walletAddress: string,
chainId: number,
): Promise<string | null> {
const rpc = rpcUrl(chainId);
if (!rpc) return null;
try {
const res = await fetch(rpc, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'eth_getBalance',
params: [walletAddress, 'latest'],
}),
});
const json = (await res.json()) as { result?: string };
if (!json.result) return null;
const wei = BigInt(json.result);
const eth = Number(wei) / 1e18;
return eth.toFixed(6);
} catch {
return null;
}
}
export async function fetchSettlementStatus(ref: string): Promise<Record<string, unknown> | null> {
const base =
process.env.SETTLEMENT_MIDDLEWARE_URL?.replace(/\/$/, '') ||
'http://127.0.0.1:3011';
const path = base.includes('/settlement')
? `${base}/api/v1/settlement/settlements/${encodeURIComponent(ref)}`
: `${base}/api/v1/settlement/settlements/${encodeURIComponent(ref)}`;
const headers: Record<string, string> = {};
const key = process.env.OMNL_API_KEY?.trim();
if (key) headers.Authorization = `Bearer ${key}`;
try {
const res = await fetch(path, { headers });
if (!res.ok) return null;
return (await res.json()) as Record<string, unknown>;
} catch {
return null;
}
}
type ChatContext = {
walletAddress?: string;
chainId?: number;
balance?: string | null;
corridors: unknown[];
ecosystemRoutes: Record<string, string>;
};
export async function buildZBotContext(
walletAddress?: string,
chainId?: number,
): Promise<ChatContext> {
const integration = loadZChainIntegration() as {
internationalCorridors?: unknown[];
brand?: { wallet?: string; bank?: string };
};
const ecosystem = loadZEcosystem() as { routes?: Record<string, string> };
const cid = chainId ?? 900002;
const balance = walletAddress ? await fetchWalletBalance(walletAddress, cid) : null;
return {
walletAddress,
chainId: cid,
balance,
corridors: integration.internationalCorridors ?? [],
ecosystemRoutes: ecosystem.routes ?? {},
};
}
function ruleBasedReply(message: string, ctx: ChatContext): string {
const lower = message.toLowerCase();
if (lower.includes('balance')) {
if (!ctx.walletAddress) {
return 'Connect your International Z Wallet first, then ask again for your balance.';
}
if (ctx.balance != null) {
return `Your Z Chain native balance is approximately ${ctx.balance} Z. For token balances, open the wallet panel.`;
}
return 'I could not read your on-chain balance right now. Check RPC configuration (CHAIN_900002_RPC_URL).';
}
if (lower.includes('iraq') || lower.includes('iran') || lower.includes('corridor') || lower.includes('send')) {
const lines = (ctx.corridors as { id: string; label: string; rails: string[] }[])
.map((c) => `${c.label} (${c.id}) — rails: ${c.rails.join(', ')}`)
.join('\n');
return `International corridors:\n${lines}\n\nUse the International transfer panel in the wallet to submit a payment. I cannot sign transactions.`;
}
if (lower.includes('transfer') && (lower.includes('status') || lower.includes('where'))) {
return 'Enter your settlement reference in the transfer status card after submitting, or paste the reference here and I will look it up on the next message with "status REF".';
}
const statusMatch = lower.match(/status\s+([a-f0-9-]{8,})/i);
if (statusMatch) {
return `Check transfer status in the wallet UI for reference ${statusMatch[1]}, or call GET /api/v1/z-wallet/status/${statusMatch[1]}.`;
}
if (lower.includes('bank') || lower.includes('z online')) {
return `Z Online Bank is at ${ctx.ecosystemRoutes.onlineBank ?? '/central-bank'} for M0M4 settlement.`;
}
return `I can help with balance, international corridors (Iraq, Iran, EU, global), transfer status, and links to Z Online Bank. Try: "What is my balance?" or "How do I send to Iraq?"`;
}
export function zBotLlmStatus(): {
provider: 'azure-openai' | 'rule-based';
configured: boolean;
deployment?: string;
apiVersion: string;
} {
const endpoint = process.env.AZURE_OPENAI_ENDPOINT?.trim();
const key = process.env.AZURE_OPENAI_API_KEY?.trim();
const deployment = process.env.AZURE_OPENAI_DEPLOYMENT?.trim();
const apiVersion = process.env.AZURE_OPENAI_API_VERSION?.trim() || '2024-02-15-preview';
const configured = Boolean(endpoint && key && deployment);
return {
provider: configured ? 'azure-openai' : 'rule-based',
configured,
deployment: deployment || undefined,
apiVersion,
};
}
export async function azureOpenAiReply(
message: string,
ctx: ChatContext,
): Promise<string | null> {
const endpoint = process.env.AZURE_OPENAI_ENDPOINT?.replace(/\/$/, '');
const key = process.env.AZURE_OPENAI_API_KEY;
const deployment = process.env.AZURE_OPENAI_DEPLOYMENT;
if (!endpoint || !key || !deployment) return null;
const system = `You are Z Bot, assistant for International Z Wallet on Z Chain (chainId ${ctx.chainId}).
Answer briefly. Never offer to sign transactions. Context: ${JSON.stringify(ctx)}`;
const apiVersion = process.env.AZURE_OPENAI_API_VERSION?.trim() || '2024-02-15-preview';
const url = `${endpoint}/openai/deployments/${deployment}/chat/completions?api-version=${apiVersion}`;
try {
const res = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'api-key': key,
},
body: JSON.stringify({
messages: [
{ role: 'system', content: system },
{ role: 'user', content: message },
],
max_tokens: 400,
temperature: 0.3,
}),
});
if (!res.ok) return null;
const data = (await res.json()) as {
choices?: { message?: { content?: string } }[];
};
return data.choices?.[0]?.message?.content?.trim() ?? null;
} catch {
return null;
}
}
export async function zBotReply(
message: string,
walletAddress?: string,
chainId?: number,
): Promise<string> {
const ctx = await buildZBotContext(walletAddress, chainId);
const llm = await azureOpenAiReply(message, ctx);
if (llm) return llm;
return ruleBasedReply(message, ctx);
}