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
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:
@@ -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
|
||||
|
||||
@@ -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" },
|
||||
|
||||
37
config/genesis-z-chain-900002.json
Normal file
37
config/genesis-z-chain-900002.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"config": {
|
||||
"chainId": 900002,
|
||||
"homesteadBlock": 0,
|
||||
"eip150Block": 0,
|
||||
"eip155Block": 0,
|
||||
"eip158Block": 0,
|
||||
"byzantiumBlock": 0,
|
||||
"constantinopleBlock": 0,
|
||||
"petersburgBlock": 0,
|
||||
"istanbulBlock": 0,
|
||||
"berlinBlock": 0,
|
||||
"londonBlock": 0,
|
||||
"arrowGlacierBlock": 0,
|
||||
"grayGlacierBlock": 0,
|
||||
"mergeNetSplitBlock": 0,
|
||||
"shanghaiTime": 0,
|
||||
"cancunTime": 0,
|
||||
"ibft2": {
|
||||
"blockperiodseconds": 2,
|
||||
"epochlength": 30000,
|
||||
"requesttimeoutseconds": 10
|
||||
}
|
||||
},
|
||||
"nonce": "0x0",
|
||||
"timestamp": "0x0",
|
||||
"gasLimit": "0x1c9c380",
|
||||
"difficulty": "0x1",
|
||||
"mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
|
||||
"coinbase": "0x0000000000000000000000000000000000000000",
|
||||
"alloc": {
|
||||
"0xfe3b557e8fb62b89f4916b721be55ceb828dbd73": {
|
||||
"balance": "0x33b2e3c9fd0803ce8000000",
|
||||
"comment": "Besu dev key #1 — funded for local deploy"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
17
config/z-chain-deployed.v1.json
Normal file
17
config/z-chain-deployed.v1.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"$schema": "Z Chain on-chain deployment addresses (chainId 900002)",
|
||||
"version": "1.0.0",
|
||||
"updated": "2026-07-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"
|
||||
]
|
||||
}
|
||||
105
config/z-chain-integration.v1.json
Normal file
105
config/z-chain-integration.v1.json
Normal file
@@ -0,0 +1,105 @@
|
||||
{
|
||||
"$schema": "International Z Wallet + Z Chain — 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"
|
||||
}
|
||||
}
|
||||
29
config/z-chain-network-config.json
Normal file
29
config/z-chain-network-config.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"genesis": {
|
||||
"config": {
|
||||
"chainId": 900002,
|
||||
"ibft2": {
|
||||
"blockperiodseconds": 2,
|
||||
"epochlength": 30000,
|
||||
"requesttimeoutseconds": 10
|
||||
}
|
||||
},
|
||||
"nonce": "0x0",
|
||||
"timestamp": "0x0",
|
||||
"gasLimit": "0x1c9c380",
|
||||
"difficulty": "0x1",
|
||||
"mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
|
||||
"coinbase": "0x0000000000000000000000000000000000000000",
|
||||
"alloc": {
|
||||
"0xfe3b557e8fb62b89f4916b721be55ceb828dbd73": {
|
||||
"balance": "0x33b2e3c9fd0803ce8000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
"blockchain": {
|
||||
"nodes": {
|
||||
"generate": true,
|
||||
"count": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
34
config/z-chain-production.v1.json
Normal file
34
config/z-chain-production.v1.json
Normal 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"
|
||||
}
|
||||
}
|
||||
15
config/z-ecosystem.local.env.example
Normal file
15
config/z-ecosystem.local.env.example
Normal 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
|
||||
41
config/z-ecosystem.v1.json
Normal file
41
config/z-ecosystem.v1.json
Normal 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": "M0–M4 banking and settlement rails"
|
||||
}
|
||||
],
|
||||
"integrationRef": "config/z-chain-integration.v1.json"
|
||||
}
|
||||
39
contracts/z-chain/AccountWalletRegistryZ.sol
Normal file
39
contracts/z-chain/AccountWalletRegistryZ.sol
Normal file
@@ -0,0 +1,39 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "@openzeppelin/contracts/access/AccessControl.sol";
|
||||
|
||||
interface IAccountWalletRegistryZ {
|
||||
struct WalletLink {
|
||||
bytes32 walletRefId;
|
||||
string provider;
|
||||
bool active;
|
||||
}
|
||||
|
||||
function linkAccountToWallet(bytes32 accountRefId, bytes32 walletRefId, string calldata provider) external;
|
||||
function getWalletsForAccount(bytes32 accountRefId) external view returns (WalletLink[] memory);
|
||||
}
|
||||
|
||||
/**
|
||||
* @title AccountWalletRegistryZ
|
||||
* @notice Minimal registry for Z Chain deploy scope
|
||||
*/
|
||||
contract AccountWalletRegistryZ is IAccountWalletRegistryZ, AccessControl {
|
||||
bytes32 public constant ACCOUNT_MANAGER_ROLE = keccak256("ACCOUNT_MANAGER_ROLE");
|
||||
|
||||
mapping(bytes32 => WalletLink[]) private _accountToWallets;
|
||||
|
||||
constructor(address admin) {
|
||||
_grantRole(DEFAULT_ADMIN_ROLE, admin);
|
||||
_grantRole(ACCOUNT_MANAGER_ROLE, admin);
|
||||
}
|
||||
|
||||
function linkAccountToWallet(bytes32 accountRefId, bytes32 walletRefId, string calldata provider) external {
|
||||
require(hasRole(ACCOUNT_MANAGER_ROLE, msg.sender), "not manager");
|
||||
_accountToWallets[accountRefId].push(WalletLink(walletRefId, provider, true));
|
||||
}
|
||||
|
||||
function getWalletsForAccount(bytes32 accountRefId) external view returns (WalletLink[] memory) {
|
||||
return _accountToWallets[accountRefId];
|
||||
}
|
||||
}
|
||||
18
contracts/z-chain/ZChainUSDC.sol
Normal file
18
contracts/z-chain/ZChainUSDC.sol
Normal file
@@ -0,0 +1,18 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
|
||||
/**
|
||||
* @title ZChainUSDC
|
||||
* @notice Minimal M2 stand-in for Z Chain local/dev deploys (cUSDC)
|
||||
*/
|
||||
contract ZChainUSDC is ERC20 {
|
||||
constructor(address admin) ERC20("Z Chain USD", "cUSDC") {
|
||||
_mint(admin, 1_000_000_000 * 10 ** 6);
|
||||
}
|
||||
|
||||
function decimals() public pure override returns (uint8) {
|
||||
return 6;
|
||||
}
|
||||
}
|
||||
149
docs/04-configuration/Z_ECOSYSTEM.md
Normal file
149
docs/04-configuration/Z_ECOSYSTEM.md
Normal 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`.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
5
frontend-dapp/.env.local.example
Normal file
5
frontend-dapp/.env.local.example
Normal 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
|
||||
145
frontend-dapp/scripts/portal-serve.mjs
Normal file
145
frontend-dapp/scripts/portal-serve.mjs
Normal 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}`);
|
||||
});
|
||||
@@ -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>
|
||||
|
||||
@@ -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`,
|
||||
|
||||
@@ -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,
|
||||
|
||||
21
frontend-dapp/src/config/zChain.ts
Normal file
21
frontend-dapp/src/config/zChain.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import zChainConfig from '../../../config/z-chain-integration.v1.json';
|
||||
|
||||
export type ZChainConfig = typeof zChainConfig;
|
||||
|
||||
export const zChain = zChainConfig;
|
||||
|
||||
export const Z_CHAIN_ID = Number(import.meta.env.VITE_Z_CHAIN_ID || zChain.chain.chainId);
|
||||
|
||||
export const zChainRpcUrl =
|
||||
import.meta.env.VITE_RPC_URL_900002 ||
|
||||
import.meta.env.VITE_Z_CHAIN_RPC_URL ||
|
||||
zChain.chain.rpcDefault;
|
||||
|
||||
export const zChainExplorerUrl =
|
||||
import.meta.env.VITE_Z_CHAIN_EXPLORER_URL || zChain.chain.explorerUrl;
|
||||
|
||||
export const zWalletEnabled =
|
||||
import.meta.env.VITE_ENABLE_Z_WALLET !== 'false' && import.meta.env.VITE_ENABLE_Z_WALLET !== '0';
|
||||
|
||||
export const Z_WALLET_OFFICE_ID = zChain.fineract.officeId;
|
||||
export const Z_HYBX_WALLET_ID = zChain.fineract.hybxWalletId;
|
||||
75
frontend-dapp/src/features/z-bot/ZBotPanel.tsx
Normal file
75
frontend-dapp/src/features/z-bot/ZBotPanel.tsx
Normal 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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
71
frontend-dapp/src/features/z-bot/useZBotChat.ts
Normal file
71
frontend-dapp/src/features/z-bot/useZBotChat.ts
Normal 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 };
|
||||
}
|
||||
111
frontend-dapp/src/features/z-bot/z-bot.css
Normal file
111
frontend-dapp/src/features/z-bot/z-bot.css
Normal 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;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
31
frontend-dapp/src/features/z-wallet/TransferStatusCard.tsx
Normal file
31
frontend-dapp/src/features/z-wallet/TransferStatusCard.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
49
frontend-dapp/src/pages/ZEcosystemHubPage.tsx
Normal file
49
frontend-dapp/src/pages/ZEcosystemHubPage.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
126
frontend-dapp/src/pages/ZWalletPage.tsx
Normal file
126
frontend-dapp/src/pages/ZWalletPage.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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
39
hardhat.zchain.config.js
Normal 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",
|
||||
},
|
||||
};
|
||||
@@ -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",
|
||||
|
||||
35
script/z-chain/DeployZChainEmoney.s.sol
Normal file
35
script/z-chain/DeployZChainEmoney.s.sol
Normal 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");
|
||||
}
|
||||
}
|
||||
11
scripts/deployment/bootstrap-z-chain-anvil.ps1
Normal file
11
scripts/deployment/bootstrap-z-chain-anvil.ps1
Normal 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
|
||||
16
scripts/deployment/bootstrap-z-chain-anvil.sh
Normal file
16
scripts/deployment/bootstrap-z-chain-anvil.sh
Normal 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
|
||||
66
scripts/deployment/bootstrap-z-chain-local.ps1
Normal file
66
scripts/deployment/bootstrap-z-chain-local.ps1
Normal 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
|
||||
70
scripts/deployment/bootstrap-z-chain-local.sh
Normal file
70
scripts/deployment/bootstrap-z-chain-local.sh
Normal 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"
|
||||
59
scripts/deployment/bootstrap-z-chain-production.sh
Normal file
59
scripts/deployment/bootstrap-z-chain-production.sh
Normal 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."
|
||||
90
scripts/deployment/complete-z-chain.ps1
Normal file
90
scripts/deployment/complete-z-chain.ps1
Normal 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"
|
||||
@@ -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
|
||||
|
||||
@@ -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)"
|
||||
|
||||
74
scripts/deployment/deploy-z-chain-contracts.js
Normal file
74
scripts/deployment/deploy-z-chain-contracts.js
Normal 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);
|
||||
});
|
||||
23
scripts/deployment/deploy-z-chain-contracts.sh
Normal file
23
scripts/deployment/deploy-z-chain-contracts.sh
Normal 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"
|
||||
52
scripts/deployment/deploy-z-ecosystem-production.sh
Normal file
52
scripts/deployment/deploy-z-ecosystem-production.sh
Normal 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)"
|
||||
133
scripts/deployment/deploy-z-ecosystem.ps1
Normal file
133
scripts/deployment/deploy-z-ecosystem.ps1
Normal 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."
|
||||
}
|
||||
136
scripts/deployment/go-live-z-ecosystem.ps1
Normal file
136
scripts/deployment/go-live-z-ecosystem.ps1
Normal 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'
|
||||
69
scripts/deployment/sync-z-chain-deployed-addresses.mjs
Normal file
69
scripts/deployment/sync-z-chain-deployed-addresses.mjs
Normal 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)');
|
||||
35
services/token-aggregation/src/api/routes/z-bot.ts
Normal file
35
services/token-aggregation/src/api/routes/z-bot.ts
Normal 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;
|
||||
43
services/token-aggregation/src/api/routes/z-wallet.ts
Normal file
43
services/token-aggregation/src/api/routes/z-wallet.ts
Normal 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;
|
||||
@@ -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)
|
||||
|
||||
200
services/token-aggregation/src/services/z-bot-context.ts
Normal file
200
services/token-aggregation/src/services/z-bot-context.ts
Normal 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 M0–M4 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);
|
||||
}
|
||||
Reference in New Issue
Block a user