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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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