wallet-android's Bridge screen has always advertised a 1 USDT minimum
(MIN_USDT constant), but usdt-bridge's actual min_deposit/min_withdraw
defaulted to 10 USDT since the tool's original scaffold commit - nobody had
ever reconciled the two. Confirmed live: a real user swap of ~1 USDT landed
correctly at the multisig custody address but was silently skipped by
listen_deposits (amount < min_deposit), so the user never received wUSDT
despite the wallet telling them 1 USDT was a valid amount.
Backend now matches the value already promised to users, not the other way
around - changing the wallet's displayed minimum after users have already
relied on it would be the wrong direction to reconcile this in.
Small/mid-size swaps no longer have to wait on 3-of-5 for every single
transaction. usdt-bridge can now execute Assets.transfer_approved directly via
the automation key's existing delegated allowance when a deposit/withdrawal
passes all three independent guards:
- max_single_tx (50,000 USDT): any single transaction above this always queues
for manual multisig review, no exceptions.
- auto_pay_daily_cap (200,000 USDT): a rolling 24h budget tracked separately
per leg (wUSDT payouts vs USDT payouts draw on separate on-chain allowances),
not a per-tx ceiling - lets many legitimate sub-cap swaps clear same-day.
- circuit breaker: if >=50% of the daily cap is consumed within 30 minutes,
auto-pay halts entirely until a human runs reset-circuit-breaker - a burst
that fast reads as a compromised key or a bug, not organic demand.
The automation key can only ever move funds via its pre-approved allowance
(never mint/burn/set_team/transfer_ownership - those stay multisig-only), so
its worst case is bounded by whatever allowance + daily cap remain, not
unbounded custody. Withdrawal auto-pay releases real USDT immediately but
defers the corresponding wUSDT burn (Owner/Admin-only, no delegation exists
for it) to a periodic manual multisig batch - tracked via a distinct
auto_paid_burn_pending status so it can't be silently forgotten.
Auto-pay is opt-in at the infra level: AUTOMATION_KEY_MNEMONIC unset means
100% manual behavior, unchanged from before. Bumped pezbridge-bot's
renewal_threshold/topup_amount so the next 3-of-5-approved allowance renewal
scales to the new daily cap.
- show_status/show_balances/show_reserves checked the wrong account:
load_wallet_addresses(&config.seed_path), the old, explicitly-retired
single-operator key, never the multisig custody addresses. These
commands were actively misleading, not just unfinished.
- listen_deposits/listen_withdrawals ran as concurrent tasks sharing one
BridgeDatabase JSON file with no locking and a non-atomic write - two
events landing close together could produce a lost update, and a
mid-write crash left a truncated file that hard-crashed the next
start. Added a process-wide async mutex around every load-mutate-save
critical section, and made save() write-temp-then-rename so a crash
mid-write can never corrupt the file in place. load() now refuses to
silently treat a corrupt file as empty (would erase replay-protection
history) - only a genuinely missing file gets a fresh database.
- The reserve-shortfall circuit breaker - the single most safety-critical
signal this tool can produce - only called a local error!() log macro.
Nobody was notified. Added real Telegram alerting (reusing
PezbridgeBot's exact chats/send pattern rather than a second channel)
for reserve shortfalls, max-single-tx-cap deposits/withdrawals needing
manual review, and every new pending_multisig_approval row.
Also removed ProcessDeposit/MintWusdt/TransferUsdt - dead-but-dangerous
CLI subcommands that still loaded the retired operator key and attempted
direct mint/transfer/burn calls that only happened to fail today because
that key lost asset authority. A latent trap, not a safe no-op by
design; production flow is listen-all -> pending_multisig_approval ->
pwap-web signing, never a single-key path.
cargo check: 0 errors, 0 warnings (both binaries).
The single-key custodial model (see prior commit's own doc comment:
"Custodial: A single keypair controls both sides") was replaced with
a 3-of-5 multisig spanning the Serok/SerokiMeclise/Xezinedar/Berdevk/
Noter tiki role-holders (full record in res/validators-tiki.md,
outside this repo). Discovered along the way: wUSDT's ~100M supply
had zero real USDT backing on Polkadot Asset Hub - Treasury_1 (the
actual asset owner, not the seed_path wallet this tool assumed) held
0 USDT/DOT there, with ~99M of the 100M supply sitting undistributed
in Treasury_1's own account. Migrated Pezkuwi Asset Hub custody in one
atomic batch (transfer 1M wUSDT to the multisig, set_team, then
transfer_ownership last) signed by Treasury_1 - tx
0xd5dc550c5cf04f323e5c1d9c13f2056acafc9f56404c657b9743c15ff3a3aacb,
verified on-chain.
listen_deposits/listen_withdrawals no longer sign and submit
mint/burn/transfer - no key this process holds has authority over the
multisig account. They now only detect on-chain events and record
status=pending_multisig_approval with the exact call parameters an
operator needs; actual approval happens through pwap-web's existing
multisig UI (shared/lib/multisig.ts, shared/lib/usdt.ts), not this
relayer. BridgeConfig gained custody_multisig_polkadot/_pezkuwi
fields.
Added pezkuwi-subxt/pezkuwi-subxt-signer alongside the existing
vanilla subxt dependency: vanilla subxt's codegen hardcodes the
`sp_runtime` module path and its default signed-extension handling
produced "bad signature" on submission against this chain's actual
metadata - pezkuwi-subxt (following vendor/pezkuwi-subxt's own
grant_noter_tiki.rs example: PezkuwiConfig, XCM v3, sudo_unchecked_weight)
is what actually signs and submits successfully. Also used to grant
the Xezinedar and Berdevk tiki roles (previously missing signatories
for the multisig) to two validators via a relay-chain sudo XCM Transact,
since the People chain has no local Sudo pallet.
chain-spec-tool:
- CLI utility for chain spec manipulation and validation
- Used for mainnet chain spec generation and verification
usdt-bridge:
- Custodial bridge for wUSDT token operations
- Handles USDT wrapping/unwrapping between external chains and Asset Hub
- Configuration in bridge_config.json