sync: mirror deployed github repo (chains + sanitized bridge), keep GitLab CI

Make this monorepo's tracked tree identical to the deployed github repo
(github.com/pezkuwichain/pezkuwi-DKS): all RC/AH/People runtimes and framework
carry the mainnet fixes (incl. U128CurrencyToVote everywhere, so the staking
election stall cannot recur), and tools/usdt-bridge matches the current,
public-safe custody tooling (real config/deploy units gitignored, secret-file
locations and server IPs stripped, sanitized *.example templates committed).

Only exception to the mirror: GitLab-native CI is preserved (.gitlab-ci.yml and
.gitlab/), to be rebuilt to match the github CI pipeline in a follow-up.
This commit is contained in:
Satoshi Qazi Muhammed committed 2026-07-24 11:42:50 -07:00
1 parent 82543bdfab
commit d285d59108
736 files changed
+15082 -16359

No files matched your search

+12 -1
View File
@@ -1 +1,12 @@
secrets/
# Real operational config & deploy units — NEVER commit. They expose chat IDs, auto-pay
# thresholds, server paths/IPs and secret-file locations. Use the *.example templates and
# provide real values only on the deployment host (env vars / EnvironmentFile / res dir).
bridge_config.json
pezbridge_bot_config.json
deploy/*.service
!deploy/*.service.example
# Runtime data & any secret material that must never reach a repo
bridge_db.json
bridge_seed.json
*_seed.json
*.env
@@ -0,0 +1,19 @@
{
"polkadot_rpc": "wss://<polkadot-asset-hub-rpc-endpoint>",
"pezkuwi_rpc": "wss://<pezkuwi-asset-hub-rpc-endpoint>",
"polkadot_usdt_asset_id": 1984,
"pezkuwi_wusdt_asset_id": 1000,
"min_deposit": 1000000,
"min_withdraw": 1000000,
"fee_basis_points": 10,
"seed_path": "<PATH_TO_BRIDGE_SEED_JSON> // prefer the BRIDGE_SEED_MNEMONIC env var; the seed file is stale for custody ops (multisig owns wUSDT)",
"db_path": "bridge_db.json",
"custody_multisig_polkadot": "<POLKADOT_3OF5_MULTISIG_SS58>",
"custody_multisig_pezkuwi": "<PEZKUWI_3OF5_MULTISIG_SS58>",
"serok_chat_id": "<TELEGRAM_ADMIN_CHAT_ID>",
"ops_chat_id": "<TELEGRAM_OPS_CHAT_ID>",
"max_single_tx": 0,
"auto_pay_daily_cap": 0,
"circuit_breaker_window_minutes": 30,
"circuit_breaker_fraction": 0.5
}
-33
View File
@@ -1,33 +0,0 @@
{
"polkadot_rpc": "wss://polkadot-asset-hub-rpc.polkadot.io",
"pezkuwi_rpc": "wss://asset-hub-rpc.pezkuwichain.io",
"polkadot_usdt_asset_id": 1984,
"pezkuwi_wusdt_asset_id": 1000,
"min_deposit": 1000000,
"min_withdraw": 1000000,
"fee_basis_points": 10,
"seed_path": "bridge_seed.json",
"db_path": "bridge_db.json",
"custody_multisig_polkadot": "15sF76THfpefUaKomHZSpssayRbsp6Yt6ESgMrLjzJCmpe66",
"custody_multisig_pezkuwi": "5GvwxmCDp3PC33KHoeWSgj3S7ocE7nzk1jiCCZMPSDBFeNcj",
"serok_chat_id": 5362991649,
"ops_chat_id": -5420200114,
"max_single_tx": 50000000000,
"auto_pay_daily_cap": 200000000000,
"circuit_breaker_window_minutes": 30,
"circuit_breaker_fraction": 0.5,
"_automation_2026-07-14": {
"note": "Small/mid swaps auto-pay via the automation key's Assets.transfer_approved allowance - no per-transaction 3-of-5 wait. max_single_tx (50,000 USDT) caps any single auto-paid transaction; auto_pay_daily_cap (200,000 USDT) is a rolling 24h budget tracked separately per leg (deposit wUSDT payouts vs withdrawal USDT payouts), NOT a per-tx ceiling - many sub-50K swaps can clear same-day. circuit_breaker_fraction/circuit_breaker_window_minutes halt all auto-pay if the daily budget is consumed abnormally fast (>=50% within 30 min) until a human runs `reset-circuit-breaker`. Anything over max_single_tx, or that would exceed the remaining daily budget, or while the breaker is tripped, ALWAYS falls back to full manual 3-of-5 review via pwap-web - this never bypasses the multisig, it only decides whether a given event needs it. Requires AUTOMATION_KEY_MNEMONIC env var to be set (see res/bridge_automation_key.json) - if unset, behavior is 100% manual, unchanged from before this feature.",
"requires_on_chain_action": "The automation key's actual Assets.approve_transfer allowance is renewed by PezbridgeBot's 3-of-5-signed renewal flow (pezbridge_bot_config.json's renewal_threshold/topup_amount) - those were bumped alongside this to top up to a size that supports the new daily cap. The NEXT renewal signers approve via pwap-web will lift the real on-chain ceiling; until then, auto-pay is bounded by whatever allowance already exists on-chain today, which is smaller. This is a fail-safe direction: if the on-chain allowance runs out, the system simply falls back to manual, never fails open."
},
"_custody_migration_2026-07-13": {
"note": "Custody is no longer a single hot key. wUSDT (asset 1000) owner/issuer/admin/freezer on Pezkuwi Asset Hub is now the 3-of-5 multisig below - seed_path/BRIDGE_SEED_MNEMONIC in this file is stale and must not be used to mint/burn/transfer_ownership going forward. listen_deposits/listen_withdrawals in src/main.rs were updated the same day: they now only detect events and record status=pending_multisig_approval - actual mint/burn/release requires 3 of the 5 signatories to approve via pwap-web's multisig UI (shared/lib/multisig.ts, shared/lib/usdt.ts).",
"multisig_threshold": 3,
"multisig_signatories": 5,
"multisig_account_pezkuwi_prefix42": "5GvwxmCDp3PC33KHoeWSgj3S7ocE7nzk1jiCCZMPSDBFeNcj",
"multisig_account_polkadot_prefix0": "15sF76THfpefUaKomHZSpssayRbsp6Yt6ESgMrLjzJCmpe66",
"pezkuwi_side_status": "live - owns wUSDT asset 1000, seeded with 1,000,000 wUSDT (tx 0xd5dc550c5cf04f323e5c1d9c13f2056acafc9f56404c657b9743c15ff3a3aacb)",
"polkadot_side_status": "address computed, zero balance - no real USDT has ever been deposited by this ecosystem on Polkadot Asset Hub; needs at least existential DOT + real USDT deposits to become useful",
"details_doc": "/home/myhez/res/validators-tiki.md"
}
}
@@ -1,53 +0,0 @@
[Unit]
Description=PezbridgeBot - USDT bridge automation-key allowance renewal notifier (holds no key)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=pezbridgebot
Group=pezbridgebot
WorkingDirectory=/opt/pezbridgebot/data
ExecStart=/opt/pezbridgebot/bin/pezbridge-bot
Environment=PEZBRIDGE_CONFIG=/opt/pezbridgebot/bin/pezbridge_bot_config.json
Environment=RUST_LOG=info
EnvironmentFile=/etc/pezbridgebot/telegram_token.env
Restart=on-failure
RestartSec=10
# --- Hardening ---
# Deployed 2026-07-13 on NEW-10 (89.116.25.180), simplified 2026-07-14: this process used to
# also hold Noter's (Validator_04's) mnemonic via LoadCredential so it could automate 1-of-3
# required signatures. That stopped being worth the risk once pwap-web shipped a real signing
# UI (any of the 5 signers can now approve directly with their own wallet extension at
# /multisig/pending) - the bot-held key only ever saved "2 humans click instead of 3", not
# enough to justify a private key living on a server. Noter's mnemonic was removed from this
# host entirely; this unit now only reads chain state and sends Telegram messages - it has no
# LoadCredential, no signing capability, and nothing worth exfiltrating. The hardening below is
# kept anyway as good baseline practice, not because there's a key to protect anymore.
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
ProtectClock=yes
ProtectHostname=yes
RestrictSUIDSGID=yes
RemoveIPC=yes
LockPersonality=yes
RestrictRealtime=yes
RestrictNamespaces=yes
MemoryDenyWriteExecute=yes
SystemCallFilter=@system-service
SystemCallArchitectures=native
CapabilityBoundingSet=
AmbientCapabilities=
ReadWritePaths=/opt/pezbridgebot/data
UMask=0077
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,49 @@
# PezbridgeBot — example systemd unit.
# Read-only allowance-renewal notifier: reads chain state and sends Telegram alerts when the
# automation-key allowance needs a 3-of-5-signed renewal. Holds NO signing key and cannot submit
# anything. Copy to /etc/systemd/system/, fill in <PLACEHOLDERS>, provide the Telegram token
# ONLY via the EnvironmentFile below (never in the repo).
[Unit]
Description=PezbridgeBot - allowance renewal notifier (holds no key)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=<service-user>
Group=<service-group>
WorkingDirectory=<WORKDIR>
ExecStart=<BIN_DIR>/pezbridge-bot
Environment=PEZBRIDGE_CONFIG=<CONFIG_PATH>/pezbridge_bot_config.json
Environment=RUST_LOG=info
EnvironmentFile=<SECRETS_DIR>/telegram_token.env
Restart=on-failure
RestartSec=10
# Hardening (recommended baseline)
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
ProtectClock=yes
ProtectHostname=yes
RestrictSUIDSGID=yes
RemoveIPC=yes
LockPersonality=yes
RestrictRealtime=yes
RestrictNamespaces=yes
MemoryDenyWriteExecute=yes
SystemCallFilter=@system-service
SystemCallArchitectures=native
CapabilityBoundingSet=
AmbientCapabilities=
ReadWritePaths=<WORKDIR>
UMask=0077
[Install]
WantedBy=multi-user.target
@@ -1,60 +0,0 @@
[Unit]
Description=usdt-bridge listen-all - USDT/wUSDT listener; auto-pays sub-cap swaps via the bounded automation-key allowance, everything else queues for 3-of-5
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=usdtbridge
Group=usdtbridge
WorkingDirectory=/opt/usdtbridge/data
ExecStart=/opt/usdtbridge/bin/usdt-bridge --config /opt/usdtbridge/bin/bridge_config.json listen-all
Environment=RUST_LOG=info
EnvironmentFile=/etc/pezbridgebot/telegram_token.env
EnvironmentFile=-/etc/usdtbridge/automation_key.env
Restart=on-failure
RestartSec=10
# --- Hardening ---
# Deployed 2026-07-14 on NEW-10 (89.116.25.180), same UTIL bucket as pezbridge-bot (no
# consensus/fund-critical services here - see dks-ci-runners/vps2-dev-decommission-status
# memory for the placement convention). UPDATED 2026-07-14: this process now also loads the
# automation key's mnemonic (from a separate, tighter-scoped EnvironmentFile than the Telegram
# token) and can sign+submit Assets.transfer_approved on both chains - but ONLY within the
# multisig's pre-approved delegated allowance, capped per-tx (max_single_tx) and per rolling 24h
# window (auto_pay_daily_cap) with a circuit breaker on burst consumption. It can never mint,
# burn, set_team, or transfer_ownership - those stay exclusively multisig (3-of-5), same as
# before. Worst case of this key being compromised is bounded by whatever allowance + daily cap
# remain at the time, not unbounded custody - a materially different (and much smaller) risk
# tier than the legacy single-key bot this whole migration replaced. Hardening kept as-is; the
# automation_key.env file itself is root:usdtbridge 0640, separate from the Telegram token file
# so a Telegram-token leak alone can never expose the signing key. The leading "-" on that
# EnvironmentFile line is required (not decorative): the file does not exist until an operator
# deliberately provisions it, and the unit must still start cleanly without it, matching the
# code's own AUTOMATION_KEY_MNEMONIC-unset fallback (100% manual, auto-pay simply inactive).
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
ProtectClock=yes
ProtectHostname=yes
RestrictSUIDSGID=yes
RemoveIPC=yes
LockPersonality=yes
RestrictRealtime=yes
RestrictNamespaces=yes
MemoryDenyWriteExecute=yes
SystemCallFilter=@system-service
SystemCallArchitectures=native
CapabilityBoundingSet=
AmbientCapabilities=
ReadWritePaths=/opt/usdtbridge/data
UMask=0077
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,50 @@
# usdt-bridge listener — example systemd unit.
# Detects USDT/wUSDT deposit/withdrawal events; auto-pays only sub-cap swaps via a bounded,
# multisig-delegated automation-key allowance, and queues everything else for 3-of-5 approval.
# It can never mint/burn/set_team/transfer_ownership — those stay exclusively with the multisig.
# Copy to /etc/systemd/system/, fill in <PLACEHOLDERS>, and provide secrets ONLY via the
# EnvironmentFile(s) below (never in the repo): BRIDGE_SEED_MNEMONIC / AUTOMATION_KEY_MNEMONIC.
[Unit]
Description=usdt-bridge listener
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=<service-user>
Group=<service-group>
WorkingDirectory=<WORKDIR>
ExecStart=<BIN_DIR>/usdt-bridge --config <CONFIG_PATH>/bridge_config.json listen-all
Environment=RUST_LOG=info
EnvironmentFile=<SECRETS_DIR>/telegram_token.env
EnvironmentFile=-<SECRETS_DIR>/automation_key.env
Restart=on-failure
RestartSec=10
# Hardening (recommended baseline)
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
ProtectClock=yes
ProtectHostname=yes
RestrictSUIDSGID=yes
RemoveIPC=yes
LockPersonality=yes
RestrictRealtime=yes
RestrictNamespaces=yes
MemoryDenyWriteExecute=yes
SystemCallFilter=@system-service
SystemCallArchitectures=native
CapabilityBoundingSet=
AmbientCapabilities=
ReadWritePaths=<WORKDIR>
UMask=0077
[Install]
WantedBy=multi-user.target
@@ -17,11 +17,11 @@ fn main() -> Result<()> {
"scheme": "sr25519",
"mnemonic": phrase,
});
std::fs::write("/home/myhez/res/bridge_automation_key.json", serde_json::to_string_pretty(&out)?)?;
std::fs::write("bridge_automation_key.json", serde_json::to_string_pretty(&out)?)?;
println!("Generated automation key.");
println!("Address (never the mnemonic): {}", ss58);
println!("Saved to /home/myhez/res/bridge_automation_key.json");
println!("Saved to bridge_automation_key.json");
Ok(())
}
@@ -105,7 +105,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== GRANT XEZINEDAR + BERDEVK TIKI ===\n");
let relay_url =
std::env::var("RPC_URL").unwrap_or_else(|_| "ws://217.77.6.126:9944".to_string());
std::env::var("RPC_URL").unwrap_or_else(|_| "ws://127.0.0.1:9944".to_string());
let validator_01: AccountId32 = "5GipBJs2uNWTCazyZQ2vG3DEqLz4tXNmNZtBAT1Mtm1orZ5i".parse()?;
let validator_02: AccountId32 = "5HWFZbhkZuTUySXu6ZXYKrTHBnWXHvWRKLozE22zhnwXGGxk".parse()?;
@@ -121,7 +121,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api = OnlineClient::<PezkuwiConfig>::from_insecure_url(&relay_url).await?;
println!("Connected to {}! specVersion: {}\n", relay_url, api.runtime_version().spec_version);
let seed_content = std::fs::read_to_string("/home/myhez/res/sudo.json")?;
let seed_content = std::fs::read_to_string("sudo.json")?;
let seed_json: serde_json::Value = serde_json::from_str(&seed_content)?;
let mnemonic_str = seed_json["mnemonic"].as_str().expect("mnemonic field present");
let mnemonic = Mnemonic::from_str(mnemonic_str)?;
@@ -35,7 +35,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api = OnlineClient::<PezkuwiConfig>::from_insecure_url(&rpc_url).await?;
println!("Connected to {}! specVersion: {}\n", rpc_url, api.runtime_version().spec_version);
let seed_content = std::fs::read_to_string("/home/myhez/res/treasury.json")?;
let seed_content = std::fs::read_to_string("treasury.json")?;
let seed_json: serde_json::Value = serde_json::from_str(&seed_content)?;
let mnemonic_str = seed_json["mnemonic"].as_str().expect("mnemonic field present");
let mnemonic = Mnemonic::from_str(mnemonic_str)?;
@@ -0,0 +1,9 @@
{
"pezkuwi_rpc": "wss://<pezkuwi-asset-hub-rpc-endpoint>",
"serok_chat_id": "<TELEGRAM_ADMIN_CHAT_ID>",
"ops_chat_id": "<TELEGRAM_OPS_CHAT_ID>",
"poll_interval_secs": 600,
"renewal_threshold": 0,
"topup_amount": 0,
"state_path": "<PATH_TO_BOT_STATE_JSON>"
}
@@ -1,9 +0,0 @@
{
"pezkuwi_rpc": "wss://asset-hub-rpc.pezkuwichain.io",
"serok_chat_id": 5362991649,
"ops_chat_id": -5420200114,
"poll_interval_secs": 600,
"renewal_threshold": 40000000000,
"topup_amount": 200000000000,
"state_path": "/opt/pezbridgebot/data/pezbridge_bot_state.json"
}
+1 -1
View File
@@ -1,5 +1,5 @@
//! PezbridgeBot - notifies signers when the USDT bridge automation key's `Assets.approve_transfer`
//! spending allowance needs renewal (see /home/myhez/res/validators-tiki.md and the
//! spending allowance needs renewal (see the operator's custody notes and the
//! usdt-bridge-multisig-migration memory for the full custody model).
//!
//! This process holds NO private key and cannot sign or submit anything. An earlier version of
+4 -4
View File
@@ -7,7 +7,7 @@
//! Backing:
//! - 1:1 backed by real USDT on Polkadot Asset Hub
//!
//! Architecture (updated 2026-07-13 - see /home/myhez/res/validators-tiki.md for the full
//! Architecture (updated 2026-07-13 - see the operator's custody notes for the full
//! migration record):
//! - Custody is a 3-of-5 multisig, NOT a single keypair. On Pezkuwi Asset Hub the multisig
//! (5GvwxmCDp3PC33KHoeWSgj3S7ocE7nzk1jiCCZMPSDBFeNcj) now owns wUSDT (asset 1000) - it holds
@@ -19,7 +19,7 @@
//! submit mint/transfer calls automatically - no key this process holds has authority over
//! the multisig account. They now only DETECT on-chain events and record them as
//! pending_multisig_approval; an actual release requires 3 of the 5 signatories (Serok,
//! SerokiMeclise, Xezinedar, Berdevk, Noter - see res/validators-tiki.md) to approve via
//! SerokiMeclise, Xezinedar, Berdevk, Noter - see the operator's custody notes) to approve via
//! `Multisig.approveAsMulti`/`asMulti`, which is expected to happen through pwap-web's
//! existing multisig UI (shared/lib/multisig.ts, shared/lib/usdt.ts, MultisigMembers.tsx),
//! not this relayer.
@@ -67,7 +67,7 @@ const AUTOMATION_KEY_ADDRESS: &str = "5GQu4PFUb1f3MTJ7i7c1CtLgDk3TVvpSW1VbQCRmfk
/// stream, the process just stops seeing new blocks forever - while staying up and logging
/// nothing further ("Listening for deposits..." was the last line for 3+ hours while a real 1
/// USDT deposit sat undetected at block #18250967, ~62 minutes into that silence - see
/// res/validators-tiki.md 2026-07-15). Checkpoint + backfill only covers a gap across a
/// the operator's custody notes 2026-07-15). Checkpoint + backfill only covers a gap across a
/// *restart*; this covers a stall with no restart at all. If no new finalized block arrives
/// within this window (Asset Hub blocks land every ~6-12s under normal conditions, so this is a
/// wide margin, not a tight one), treat the subscription as stalled and force a full reconnect -
@@ -1317,7 +1317,7 @@ async fn listen_deposits(config: &BridgeConfig, automation: &AutomationContext)
// Checkpoint + backfill: a live block subscription alone is not a correctness guarantee - a
// transient reconnect or subscription hiccup can silently skip blocks with zero logging (this
// is exactly how a real user deposit went undetected in production - see
// res/validators-tiki.md 2026-07-15). On every (re)start, explicitly scan any gap between
// the operator's custody notes 2026-07-15). On every (re)start, explicitly scan any gap between
// the last persisted checkpoint and the current chain head before relying on the live stream.
let last_processed = { BridgeDatabase::load(&config.db_path)?.stats.last_polkadot_block };
let head_hash = legacy_rpc.chain_get_finalized_head().await?;