Bittensor SN7 · Allways · Incident report

Three miners slashed for deliveries that settled on-chain

Between 06:22 and 07:10 UTC on 20 October 2026, four swaps against three unrelated miners resolved TIMED_OUT and paid the taker 38.71 SOL and 5.48 TAO out of miner collateral. In every case the miner's destination payment is on-chain, confirmed, and for the exact required amount — settled roughly ten minutes before the timeout verdict was cast.

Filed 2026-10-20 Reporter: miner uid 148 Hotkey 5D4yUDW58SiD…
4slashes in 37 minutes, one taker
3unrelated miners hit
38.71SOL moved to the taker, plus 5.48 TAO
0timeout extensions used on any of the four

The four slashes

One taker, 3ZXhCercm2A3…, opened every one of these swaps.

Time UTCMinerLaneSize Fulfilled afterVerdict afterExt.Penalty to taker
06:22:244V4pPw72XBaJ…sol→solusdc$1,252.2455 s666 s013.189 SOL
06:34:01FneiDSnQEUpu…sol→solusdc$1,242.842 s611 s013.090 SOL
06:48:218XaVJcizxHg3… uid 148sol→tao$1,319.8420 s614 s05.4809 TAO
06:59:158XaVJcizxHg3… uid 148sol→solusdc$1,182.092 s606 s012.430 SOL

The penalty is 110% of the swap: a 100% refund to the taker plus a 10% miner penalty. Because the miner had already delivered, the taker collected both the goods and a refund for those same goods.

Two of the four killed uid 148 outright. Its SOL purse fell 13.2 → 0.312 and its TAO bond 5.5 → 0.017, dropping both below the 1.0 minimum and deactivating the miner.

Delivery landed; the verdict came ten minutes later

Each track runs from swap initiation to the timeout verdict. Green marks when the miner's payment was on-chain and mark_fulfilled succeeded; red marks when the validator voted to slash.

4V4pPw7206:22 · sol→solusdc
55 s delivered
666 s slashed
FneiDSnQ06:34 · sol→solusdc
2 s delivered
611 s slashed
uid 14806:48 · sol→tao
20 s delivered
614 s slashed
uid 14806:59 · sol→solusdc
2 s delivered
606 s slashed
0 s 200 s 400 s 600 s
delivery on-chain & mark_fulfilled accepted TimeoutSwap executed, collateral paid to taker

Proof each delivery settled

Every destination payment is on-chain, succeeded with no error, and credits the taker's own receiving account with exactly the required amount. "Required" is 99% of the pinned to_amount; the 1% is the protocol fee, and all 93 completed fills by uid 148 show the identical 1.000% deduction.

The clearest single proof

The taker's TAO address 5ExiT5xqmamUwRaFwYXbwEM5A9EheWsBrUYrCKRHJMMqJiRU currently holds 10.3135 TAO:

  4.9328 TAO   the goods uid 148 delivered  (tx 0x55def5b2…, 7s after discovery)
  5.4809 TAO   the refund the protocol paid  for those same goods
 ──────────
 10.4137 TAO   (10.3135 after fees) = the balance standing in that account

The taker was paid twice for one swap, out of the miner's bond.

Miner-side trace, 06:59 swap

uid 148's logs show a single clean fulfilment — no retry, no duplicate send.

06:59:31.553  Discovered swap c638c39c: sol -> solusdc, status=Active
06:59:31.703  source funds verified (11300000000 from 3ZXhCercm2...)
06:59:31.703  initiating dest send of 1171726380 to 3ZXhCercm2... on solusdc
06:59:32.429  sent 1171726380 (tx: 3CCTAXE9..., block: 459139383)
06:59:33.037  marked as fulfilled          <- SUCCESS, on-chain
07:09:55.222  TIMED OUT - collateral slashed (failed_swaps=2)

On-chain the miner emitted exactly one SubmitSwapClaim, one VoteInitiate and one MarkFulfilled, all under swap_key c638c39c. Across the whole episode uid 148 sent 5,905.22 USDC in five deliveries plus one TAO delivery — one payment per swap, no duplicates.

For comparison, the swap immediately before it (884c8f51, 06:51:49, same taker, same lane, same size band) received its ConfirmSwap 23 seconds after MarkFulfilled. The two slashed swaps never received one at all.

Where the verification fails

We have not seen validator logs, so the per-swap reject reason is yours to pull. But in allways/validator/solana_swap_loop.py there is exactly one path that turns a settled delivery into a slash, and the forensics point straight at it.

A lagging node and an absent payment are indistinguishable

_decide_fulfilled verifies the destination leg through _fetch_leg, which collapses three very different situations into two verdicts:

try:
    info = provider.verify_transaction(...)
except ProviderUnreachableError:
    return ('down', None)      # never slashes
if info is None:
    return ('no', None)        # slash-eligible
if not info.confirmed:
    return ('pending', info)   # extendable
return ('ok', info)

The safe 'down' verdict is reached only when the provider raises a connection error. A node that answers normally but has not yet indexed the delivery returns None, which becomes 'no' — byte-for-byte the same verdict as a miner who never paid at all. From there the overdue branch slashes:

return SwapAction(SwapDecision.TIMEOUT, reason=f'{why} + overdue — dest unverifiable, slashing')

The zero-extension evidence

The loop already has the right machinery for a delivery it cannot yet see: 'pending' routes to EXTEND_TIMEOUT, which slides timeout_at instead of slashing. All four swaps record timeoutExtensionsUsed = 0. The extension path was never entered, so the destination leg was not read as unconfirmed — it was read as absent, for transactions that had been final on-chain for roughly ten minutes.

The contract never consults the delivery it already holds

MarkFulfilled writes the destination tx hash onto the swap PDA; the chain knew 3CCTAXE9… the whole time. The timeout path ignores it and pays the taker a full refund anyway. The code says so plainly:

TIMEOUT an overdue swap whose dest is absent/mismatched (the contract slashes Fulfilled too).

So a swap sitting in Fulfilled state, carrying a valid delivery hash, can still be refunded in full with nothing asking — did the miner already pay?

What the payout transaction looks like

The miner neither signs it nor can block it. For uid 148's 06:59 swap, transaction 3MwqCU2ccuDDysrNvC59be… at 07:09:36:

Instruction: TimeoutSwap
Signer:  AuRf6YidcPWGBt4PALPxW42VyuRRpje9pSGRvmv4bMsm   (validator)
miner a signer?  no

8qdd5wmUrxzm34p6miHNAX4eLLjWN6frXE4bTzMGn8qW   -12.4300 SOL   (miner collateral PDA)
3ZXhCercm2A3F3zCVJg4QiaMKAjNBGLja5Wif56yciaM   +12.4300 SOL   (the taker)

The same instruction paid the taker 13.189 SOL at 06:33:30 and 13.090 SOL at 06:44:38 for the other two miners.

Why this is exploitable, not merely unlucky

The taker's downside on a normal, completing swap is only the 1% protocol fee — about $12 on a $1,200 fill. Their upside when the false negative fires is the miner's entire posted collateral, because the penalty is 110% of the swap and a miner at full capacity must post 1.1× the maximum fill.

Risking roughly $12 to win roughly $1,300 is worth spamming, and the larger the fill, the larger the prize. That matches what we observed: maximum-size fills aimed at the deepest crown holders, repeated until one failed to confirm.

The taker, and who benefited

A purpose-built wallet

3ZXhCercm2A3F3zCVJg4QiaMKAjNBGLja5Wif56yciaM was created at 00:16 UTC on the morning of the attack and funded with 28.405 SOL from 6wWVbLeHDVHvCCsLprB85jkbU1JhS9VezABT2ZZFJ5BB. That funder holds about 2,799 SOL and moves through 1,000+ transactions a day across hundreds of counterparties — an exchange hot wallet, so the ownership trail stops there by design.

The attack funded itself

It deposited 69.59 SOL into uid 148 across six swaps while never holding more than about 28 SOL, because the delivered USDC was swapped straight back into SOL on Jupiter between fills:

Time UTCInstructionCredit to taker
06:28:56RouteV2+23.82 SOL
06:36:21Fill+11.79 SOL
06:44:47Fill+21.81 SOL

Three miners were drained largely with the same capital going round, topped up each time by a refunded collateral purse.

The beneficiary — circumstantial, for you to verify

From 07:01, minutes after uid 148 went down, the same taker wallet began filling miner 2NX58Qr3N59RuHnNHWBZsDq6FCL1oLn5NLwUSBfVm1AE — uid 174, hotkey 5FjuPi5gAFnM2dWsjRd3rki2ffKLMcw2oMh3uQgwgRQazDfk. What we can show:

  • uid 174's Solana wallet was created at 01:52 the same morning, 96 minutes after the taker wallet.
  • 22 direct transactions between the two, in round trips of 0.90–0.95 SOL — the size and cadence of eligibility self-fills, not of organic demand.
  • uid 174 holds 0 stake and 0 incentive.

We are not asserting common ownership

The exchange hop breaks the funding trail, and we found no direct link between uid 174 and the taker's funder. Two readings fit the data: the same operator clearing the deep crown holders and then keeping its own purses alive, or an unrelated small miner that happens to be the taker's next counterparty.

One detail cuts both ways. uid 174 itself took a TimeoutSwap of 0.99 SOL from the same taker at 07:25:22 — either a genuine fourth victim, or cheap camouflage, since a self-dealt slash costs the operator only the 10% penalty. You have the registration and payment records to settle it. We are flagging the pattern, not the verdict.

What we are asking for

1 · Reimbursement

For uid 148 the two penalties were 12.430 SOL and 5.4809 TAO. The true economic loss is smaller than the collateral drain, and we would rather be precise than over-claim:

ComponentAmount
The 10% penalty itself (1.13 SOL + 0.498 TAO)−$242
Goods delivered to the taker and never paid for−$2,431
Margin earned on the four fills that did complete+$48
Net loss to uid 148−$2,625

The 100% refund portion is not itself a loss — that money was the taker's deposit, which we still hold. The loss is that we shipped the goods and refunded them. We would accept reimbursement of the penalties in kind (12.430 SOL + 5.4809 TAO) and leave the arithmetic above as the audit trail.

Miners 4V4pPw72… (13.189 SOL) and FneiDSnQ… (13.090 SOL) were hit by the same taker in the same window and are presumably owed the same treatment. We are not acting for them and have not contacted them.

2 · Reset the failed_swaps counters

This matters as much as the money. uid 148 now sits at failed 2 of 3; one more false negative is a permanent eligibility lockout for an identity that has never actually missed a delivery. Both strikes came from deliveries that are final on-chain. Please roll them back for all affected miners, or the bug quietly converts into a permanent ban.

3 · Two missing guards

  1. Distinguish "not delivered" from "not yet visible." In _fetch_leg, a provider that answers but has not indexed the transaction should not produce the same verdict as a provider that confirms the transaction is absent. Returning 'down' or 'pending' there routes to the EXTEND_TIMEOUT machinery that already exists and was never used here. A cross-check against a second RPC, or a quorum requirement before a slash, would also close it.
  2. Re-verify the stored hash before paying. The swap PDA already holds the delivery hash written by MarkFulfilled. A final verification pass against that hash immediately before TimeoutSwap executes would have prevented all four of these. As it stands the contract refunds a Fulfilled swap without ever consulting the delivery it is holding.

A cheaper partial mitigation, if the verification change is slow to land: for a swap in Fulfilled state carrying a destination hash, cap the penalty at the 10% rather than 110%. That alone takes the jackpot out of the attack.

4 · Everything here is independently verifiable

All transaction hashes, swap keys and timings above come from the public Solana and Bittensor chains and from api.all-ways.io/swaps. Nothing rests on our own logs except the miner-side trace, which is corroborated by the on-chain SubmitSwapClaim / VoteInitiate / MarkFulfilled sequence.