Evidence - peaq root key cancelled an unstake on 2026-07-12
Evidence - peaq root key cancelled an unstake on 2026-07-12
Forensic record for account 0x076727fD87bf5f02E5262314C5fbcCA53545AFC1. Every fact below was read from peaq mainnet state and block data; the verification method for each is given at the bottom.
Account under examination
| EVM address | 0x076727fD87bf5f02E5262314C5fbcCA53545AFC1 |
| Substrate account | 5DrLJKz6PUAjzK4Cwev7CA3Ut8yU41NDvTu2YfaasG2d5UuT |
| relationship | default Frontier derivation blake2_256("evm:" ++ h160) - unclaimed, no Substrate private key exists |
| chain | peaq mainnet (peaq-network), specVersion 110, SS58 prefix 42, EVM chain id 3338 |
1. The unstake that was cancelled
Transaction 0x996a6bacc2c0ffd2d48e36a0f1062dd919fa527dd2b5e0a0c2dc27bbaab923b3
| block | 10,641,119 (0xa25edf) |
| from → to | 0x076727fD87bf5f02E5262314C5fbcCA53545AFC1 → 0x0000000000000000000000000000000000000807 (parachain-staking precompile) |
| call | delegatorStakeLess(bytes32,uint256), selector 0xb7e8947f, nonce 749 |
| amount | 632,912.89 PEAQ (0x860646c9cacac9590000) |
| resulting chunk | unlock at block 10,842,719 = 10,641,119 + 201,600 (StakeDuration) |
2. The cancellation
Block 10,839,084 - 2026-07-12T16:41:36Z - 3,635 blocks (~8.1 hours) before the chunk would have unlocked.
multisig.asMulti signed by 5DuKzHP6BiCdMZtCuHq1Bio5FvpqtUEhwuUTwev34UJoiVfW
└─ sudo.sudoUncheckedWeight
└─ sudo.sudoAs(5DrLJKz6PUAjzK4Cwev7CA3Ut8yU41NDvTu2YfaasG2d5UuT) ← dispatched AS the account under examination
└─ parachainStaking.delegatorStakeMore(collator, 632,912.89 PEAQ)
Events emitted in that block:
parachainStaking.DelegatorStakedMore [5DrLJKz6PUAjzK4Cwev7CA3Ut8yU41NDvTu2YfaasG2d5UuT, 5CAWdUMni9HUmCaxJ8fP37EMaVe9apCmW3tCMp4R9m3vFXEP, 19,543,840.249191565229000000 → 20,176,753.139191565229]- delta exactly 632,912.89 PEAQ, matching the pending chunk to the tokensudo.SudoAsDone { Ok }sudo.Sudid { Ok }multisig.MultisigExecuted { approving: 5DuKzHP6BiCdMZtCuHq1Bio5FvpqtUEhwuUTwev34UJoiVfW, timepoint: { height: 10,837,080, index: 2 }, multisig: 5GeZPhuVtn6Q9h3rQcZPEuEsczKE5EaghpGdFoamE4bp7Bic, callHash: 0xfc0fe9bb6eb322664e85270d3fab42c87f7b54773505ed14c79580f6095bb5f9 }- fee 0.002152101934920413 PEAQ, paid by
5DuKzHP6BiCdMZtCuHq1Bio5FvpqtUEhwuUTwev34UJoiVfW
Signatories - multisig 5GeZPhuVtn6Q9h3rQcZPEuEsczKE5EaghpGdFoamE4bp7Bic, threshold 2 of 4:
| role | account |
|---|---|
initiated (timepoint 10,837,080, ~4.5 h earlier; its 0.26 PEAQ deposit is refunded in the same block via balances.Unreserved) | 5HMB2tcnaoGJfrxk5x53HAuRx5HRqdKTuTSmchRdgojLS5xR |
| executed, paid the fee | 5DuKzHP6BiCdMZtCuHq1Bio5FvpqtUEhwuUTwev34UJoiVfW |
| other co-signers | 5GQbNeMzL5eywZi7DPKrrASUQYNU39aCksm5jYcsGp2ekyYy, 5GcDEue4J86Msu83SacFvf2tvCJ1cF9HMmjwsyymqg6GM5DN |
3. That multisig is peaq's chain root key
-
sudo.key()==5GeZPhuVtn6Q9h3rQcZPEuEsczKE5EaghpGdFoamE4bp7Bic, verified both at block 10,839,084 and at present head. -
The signatory set is cryptographically confirmed, not assumed. A Substrate multisig address is
blake2_256("modlpy/utilisuba" ++ sorted(signatories) ++ threshold). Deriving it from the four accounts listed in §2 with threshold 2 reproduces5GeZPhuVtn6Q9h3rQcZPEuEsczKE5EaghpGdFoamE4bp7Bicexactly. Those four accounts, and no others, control root; two of them suffice to act. -
Custody chain, each link signed by its predecessor (the only way
sudo.setKeycan be called):when block sudo key 2024-01-03 (genesis era) 1 5Cfmbo2WGEnhLctZa7Jt6BWrZci1RwPfdzqf8jBTghpiXxJxby 2024-03-29 ~500,000 5GLWXiETENCdieTVpFYfsSAqZzUseMoAEPMEofnTaoVuymtS2024-11-09T13:31:36Z 2,594,905 5GeZPhuVtn6Q9h3rQcZPEuEsczKE5EaghpGdFoamE4bp7BicThe final rotation is extrinsic #2 of block 2,594,905:
sudo.setKey { new: 5GeZPhuVtn6Q9h3rQcZPEuEsczKE5EaghpGdFoamE4bp7Bic }signed by the previous sudo key5GLWXiETENCdieTVpFYfsSAqZzUseMoAEPMEofnTaoVuymtS, emittingsudo.KeyChanged { old: 5GLWXiETENCdieTVpFYfsSAqZzUseMoAEPMEofnTaoVuymtS, new: 5GeZPhuVtn6Q9h3rQcZPEuEsczKE5EaghpGdFoamE4bp7Bic }. -
It authorizes peaq's runtime upgrades - confirmed by decoding raw chain data, independently of any indexer. Three upgrade authorizations were fetched from the node and decoded locally:
block extrinsic signed by outcome 6,331,626 multisig.asMulticontainingparachainSystem.authorizeUpgrade5DuKzHP6BiCdMZtCuHq1Bio5FvpqtUEhwuUTwev34UJoiVfW(signatory)success 6,149,603 multisig.asMulticontainingparachainSystem.authorizeUpgrade5GQbNeMzL5eywZi7DPKrrASUQYNU39aCksm5jYcsGp2ekyYy(signatory)success 5,452,470 multisig.asMulticontainingparachainSystem.authorizeUpgrade5GQbNeMzL5eywZi7DPKrrASUQYNU39aCksm5jYcsGp2ekyYy(signatory)success In each, the
other_signatorieslist is exactly the remaining three accounts of the same 2-of-4 set. Indexed history shows 17authorize_upgradeand 9enact_authorized_upgradecalls in total through this key. specVersion during its tenure: 103 → 104 → 105 → 106 → 109 → 110. The only other root path, the council, has 6 members and 1 proposal in the chain's entire history.This is the key that upgrades peaq's runtime. It is not a lookalike, a stale key, or a misread.
-
The rotation was a hardening step: a single key (
5GLWXiETENCdieTVpFYfsSAqZzUseMoAEPMEofnTaoVuymtS) handed authority to a 2-of-4 multisig, adding three required co-signers. -
peaq runs the stock
pallet-sudo, not a modified fork:runtime/peaq/Cargo.tomldeclares the upstreampallet-sudoworkspace dependency, and the repo's ownpallets/directory contains onlyaddress-unification,block-reward,inflation-manager,parachain-staking,xc-asset-config. Every call in upstream pallet-sudo (sudo,sudoAs,sudoUncheckedWeight,setKey) begins by requiringsender == sudo.key(). So the actor is, by construction, whoever controls that multisig - and a multisig call additionally requires 2 of its 4 keys.
Therefore: the cancellation was performed by peaq's legitimate, long-standing chain root authority. There is no mechanism in the runtime by which compromising a user wallet key confers sudo - sudo can only be handed over by the current sudo holder.
4. Nothing was taken - this was not a liquidity grab
The sudoAs call re-staked the funds into this account's own delegation, with the collator it was already delegating to. No value moved to peaq or anywhere else. Free balance across the cancel block, unchanged to the last digit:
| block | free balance | reserved |
|---|---|---|
| 10,839,083 | 2,533,283.441872764086297866 PEAQ | 0 |
| 10,839,084 (the cancel) | 2,533,283.441872764086297866 PEAQ | 0 |
| 10,839,085 | 2,533,283.441872764086297866 PEAQ | 0 |
The only effect was to convert 632,912.89 PEAQ from about to be withdrawable back into staked and locked. The sole beneficiary of the action is whoever wants these funds to stay put.
5. The 2026-07-12 action in isolation
- Block 10,839,084 contains exactly one
DelegatorStakedMoreevent, for this account and no other. - It is the only sudo activity in the 401-block window 10,838,884 - 10,839,284 (~54 minutes).
- The multisig was initiated ~4.5 hours before execution - a deliberate, planned, multi-party action.
- These are rarely-used keys. Lifetime extrinsic counts (account nonce) of the four signatories:
5HMB2tcnaoGJfrxk5x53HAuRx5HRqdKTuTSmchRdgojLS5xR53,5DuKzHP6BiCdMZtCuHq1Bio5FvpqtUEhwuUTwev34UJoiVfW34,5GQbNeMzL5eywZi7DPKrrASUQYNU39aCksm5jYcsGp2ekyYy23,5GcDEue4J86Msu83SacFvf2tvCJ1cF9HMmjwsyymqg6GM5DN5. peaq's root authority has been exercised a small number of times in total, so this was a hand-made intervention, not automation.
5a. Full-history audit: every sudoAs ever made on peaq targets this one account
Indexed audit of every extrinsic ever signed by all six accounts that have held or exercised sudo (the four current multisig signatories, plus both earlier sudo keys) - 121 sudo/multisig extrinsics in total, each decoded.
Result: 15 sudo.sudo_as calls exist in peaq's entire history. All 15 name 5DrLJKz6PUAjzK4Cwev7CA3Ut8yU41NDvTu2YfaasG2d5UuT. None names any other account.
They correspond to ~8 distinct interventions (each needs 2 multisig approvals):
| date | block(s) | call dispatched as this account | amount |
|---|---|---|---|
| 2025-11-20 | 7,842,458 | join_delegators | 2,533,682.1 PEAQ |
| 2026-01-03 | 8,459,892 · 8,460,031 | join_delegators | 2,533,682.1 PEAQ |
| 2026-01-16 | 8,641,809 · 8,641,828 | join_delegators | 2,533,682.1 PEAQ |
| 2026-05-06 | 10,064,460 · 10,065,747 | delegator_stake_more | 2,531,751.57 PEAQ |
| 2026-05-06/07 | 10,066,088 · 10,072,495 | join_delegators | 2,531,751.57 PEAQ |
| 2026-05-22/23 | 10,254,121 · 10,266,514 | delegator_stake_more | 1,300,000 PEAQ |
| 2026-06-12 | 10,497,076 · 10,499,398 | delegator_stake_more | 59,000 PEAQ |
| 2026-07-12 | 10,837,080 · 10,839,084 | delegator_stake_more | 632,912.89 PEAQ |
The 2026-07-12 entry is the cancellation documented above. It is the eighth such intervention, not an isolated event: each time this account's funds were unstaked, root re-staked them. The pattern runs continuously from 2025-11-20 to 2026-07-12.
For completeness, root has also used force_transfer (20×), force_remove_vesting_schedule (10×) and force_set_balance (1×) elsewhere on the chain - so root does act on other accounts, but never once via sudoAs, and never to reverse another account's unstaking.
Method: Subscan indexed extrinsic history for 5HMB2tcnaoGJfrxk5x53HAuRx5HRqdKTuTSmchRdgojLS5xR (53 extrinsics), 5DuKzHP6BiCdMZtCuHq1Bio5FvpqtUEhwuUTwev34UJoiVfW (34), 5GQbNeMzL5eywZi7DPKrrASUQYNU39aCksm5jYcsGp2ekyYy (23), 5GcDEue4J86Msu83SacFvf2tvCJ1cF9HMmjwsyymqg6GM5DN (5), 5GLWXiETENCdieTVpFYfsSAqZzUseMoAEPMEofnTaoVuymtS (39, previous sudo key), 5Cfmbo2WGEnhLctZa7Jt6BWrZci1RwPfdzqf8jBTghpiXxJx (7, first sudo key). Every as_multi and sudo.* payload was decoded and its account arguments extracted. approve_as_multi / cancel_as_multi carry only a call hash and add no targets.
5b. Was the root key itself acting as a thief? The evidence says no
The obvious suspicion, given that this account is the only sudoAs target in the chain's history, is that the root holders are themselves the attackers. Three findings weigh against it:
- They can move funds arbitrarily, and never moved these. Root has used
balances.force_transfer20 times on other accounts (e.g. block 10,577,019:5EYCAe5cKPAoFioiNofJ5BLTBbhsYn7anzfPTNUi1mvrMFdv→5Dkn5fEkWshwmxunwgM8Z5FzN7upMChjG58vrdfNi2Md4Mf6, a sweep of block-reward pot accounts) andforce_set_balanceonce. Taking this account's 2.53M PEAQ would have been a single call, available at any moment for nine months. It was never made. - Nothing has ever left this account by root's hand. Every one of the 15
sudoAscalls is a staking call -join_delegatorsordelegator_stake_more- with the funds remaining on the account throughout. Balances across the 2026-07-12 cancellation are identical to the last digit (§4). - None of root's
force_transfer/force_remove_vesting_scheduleactions touch this account. Decoded from chain data at blocks 10,642,172, 10,642,742, 10,577,019 and 10,566,254 - the targets are other accounts (0x74cc13dc7f50d645a07ffc9e398507b57ede27d7,5EYCAe5cKPAoFioiNofJ5BLTBbhsYn7anzfPTNUi1mvrMFdvreward pots), not5DrLJKz6PUAjzK4Cwev7CA3Ut8yU41NDvTu2YfaasG2d5UuT.
A root holder intent on stealing would transfer, not repeatedly re-lock. The observed behaviour immobilizes the funds while leaving ownership untouched.
The counter-argument, stated fairly: if root's aim were to protect a compromised account, it could have force-transferred the balance to a safe address - as it demonstrably knows how to do. It did not. One explanation is that moving funds requires choosing a beneficiary, which is impossible to do safely while ownership is contested or unverified; re-staking is the minimal intervention that blocks extraction without root taking custody or adjudicating ownership. That is consistent with the evidence but is not established by it.
6. Why re-staking destroys a pending unstake
pallets/parachain-staking/src/lib.rs, increase_lock():
// update Unstaking by consuming up to {amount | more}
<Unstaking<T>>::try_mutate(who, |unstaking| -> DispatchResult {
// reduce {amount | more} by unstaking until either {amount | more} is zero or
// no unstaking is left
// if more is set, we only want to reduce by more to achieve 100 - 40 + 30 = 90
// locked
let mut amt_consuming_unstaking = if more.is_zero() { amount } else { more };
unstaking_len = unstaking.len().saturated_into();
for (block_number, locked_balance) in unstaking.clone() {
if amt_consuming_unstaking.is_zero() {
break;
} else if locked_balance > amt_consuming_unstaking {
// amount is only reducible by locked_balance - amt_consuming_unstaking
let delta = locked_balance.saturating_sub(amt_consuming_unstaking);
// replace old entry with delta
unstaking
.try_insert(block_number, delta)
.map_err(|_| Error::<T>::NoMoreUnstaking)?;
amt_consuming_unstaking = Zero::zero();
} else {
// amount is either still reducible or reached
amt_consuming_unstaking =
amt_consuming_unstaking.saturating_sub(locked_balance);
unstaking.remove(&block_number);
}
}
Ok(())
})?;
// Either set a new lock or potentially extend the existing one if amount
// exceeds the currently locked amount
T::Currency::extend_lock(STAKING_ID, who, amount, WithdrawReasons::all());
Any stake-increase consumes pending unstaking chunks to fund the new stake. The chunk is destroyed, not shortened or paused - the full 201,600-block (~18-19 day) wait restarts from zero on the next unstake. increase_lock is reached from join_delegators, delegator_stake_more, delegate_another_candidate, join_candidates and candidate_stake_more.
7. State as of 2026-07-28 (block ~11,013,900)
| free | 2,532,440.54589810641383412 PEAQ |
| frozen (largest lock) | 2,531,751.57 PEAQ |
| transferable | 688.97589810641383412 PEAQ |
| active delegation | 100 PEAQ (= MinDelegatorStake) to 5CAWdUMni9HUmCaxJ8fP37EMaVe9apCmW3tCMp4R9m3vFXEP |
| unstaking chunk 1 | 1,500,000 PEAQ - submitted 2026-07-26 (block 10,992,058), unlocks block 11,193,658 (~Aug 14) |
| unstaking chunk 2 | 1,031,651.57 PEAQ - submitted 2026-07-27 (block 11,004,505), unlocks block 11,206,105 (~Aug 15) |
| vesting | 3,021,514.55 PEAQ original, ~817,455 still locked, fully vests ~2027-02-12 |
Both current chunks are exposed to the same cancellation, and the July precedent came ~8 hours before maturity - so speed on unlock day is no defence against a repeat.
8. Proven vs. inferred
Proven by chain data: that the actor was the account holding sudo.key(); how (sudoAs -> delegatorStakeMore); when; that no value left the account; that this account is the only account ever named in a sudoAs call on peaq, across ~8 interventions since 2025-11-20; and that a compromised user wallet cannot yield sudo.
Inference, not proof - identity. Chain data never proves who holds a key. That this multisig is "the peaq team" follows from it holding root since 2024-11-09 and authorizing the chain's runtime upgrades (observed directly, §3) on a chain running peaq's official node builds: whoever controls it controls peaq. The residual alternative - that peaq's own 2-of-4 root multisig is itself compromised - cannot be excluded from chain data, though anyone holding it could mint or drain the entire chain rather than re-stake one account. The identities behind the four signatory addresses are not verifiable on-chain.
Not determinable from chain data: why, and whether root acted on its own initiative or was induced to act by a third party. Two readings fit the facts - (a) peaq believes the account is compromised and is keeping funds away from whoever holds the key, noting they cannot distinguish the owner's withdrawal from a thief's since both use the same key; or (b) peaq treats this allocation as contractually locked and is enforcing that. Only peaq can say which.
9. How to verify independently
- The cancel -
peaq.subscan.io, block 10,839,084: extrinsicmultisig.asMultiand theDelegatorStakedMore/SudoAsDone/Sudidevents. - The root key - chain state query
sudo.key(), at present head and at block 10,839,084. - The rotation - block 2,594,905, extrinsic
sudo.setKeyand eventsudo.KeyChanged. - The chunk's disappearance - historical storage query
parachainStaking.unstaking(5DrLJKz6PUAjzK4Cwev7CA3Ut8yU41NDvTu2YfaasG2d5UuT): the entry keyed 10,842,719 is present at block 10,839,083 and absent at 10,839,084. (Located by binary search over the range 10,641,120 → head.) - The original unstake -
eth_getTransactionByHash("0x996a6bacc2c0ffd2d48e36a0f1062dd919fa527dd2b5e0a0c2dc27bbaab923b3")on any peaq EVM RPC.
Block hashes, so the referenced blocks can be pinned exactly:
| block | hash |
|---|---|
| 10,839,084 (the cancellation) | 0x19cf553ae08d474aa42540b0cc6c295a25291f1f8e6c5817cf34b1f754136707 |
| 10,641,119 (the unstake) | 0xab283a2a71fb1cc56d065f641da6603f2f78997c674f118871faed38d3a3976d |
| 2,594,905 (the sudo rotation) | 0x09bd2bbf640b6e0b104631d16d4c79c20b478276c99effa3357407b949e9e43b |
Public RPC endpoints used (no key required): wss://peaq.api.onfinality.io/public, wss://mpfn1.peaq.network. EVM JSON-RPC: https://evm.peaq.network.