❌

Normal view

There are new articles available, click to refresh the page.
Yesterday — 28 October 2026Coinmonks

Ten EIP-7702 delegates strip their metadata hash. Nine of them publish their source

28 October 2026 at 02:59

I’d marked them as the likely hidden code. Of the 46 delegates that keep the hash, fewer than half are verified.

The most delegated contract in my EIP-7702 sample ends in these 12 bytes: a164736f6c6343000822000a. Most Solidity contracts built with default settings end in 53. The longer tail is a small CBOR map the compiler appends to the bytecode: an IPFS hash of the contract’s metadata file, the compiler version, and two bytes giving the map’s length. This one has the version and no hash.

On 8 September I pulled every EIP-7702 authorization from a 200-block window on Ethereum mainnet, 1,178 of them, and read the bytecode of the 59 contracts they delegated to. Nine of those authorizations pointed at the zero address, which is how an account undelegates, so 1,169 went to real code. When I went back over the 59 on 11 October, ten had the short tail. I left a note beside them: no metadata hash, so maybe nobody meant the source to be found.

What the 12 bytes still say

Read as CBOR, a1 opens a map with one entry, 64 says a four-byte string follows (solc), and 43 says a three-byte value follows: 00 08 22, which is version 0.8.34. The last two bytes, 00 0a, give the length of everything before them, ten. So the compiler version survives. What’s gone is the hash that would let anyone fetch the exact metadata file, the one that lists every source file’s own hash and the compiler settings. In solc that’s a single setting, metadata.bytecodeHash set to none, and Foundry exposes it as bytecode_hash.

Two byte strips. The upper one, from the second most delegated contract, is 53 bytes: a CBOR map holding an IPFS hash of 34 bytes and the solc version 0.8.35, then the length 0x33. The lower one, from the most delegated contract, is 12 bytes: a map holding only the solc version 0.8.34, then the length 0x0a.
The last bytes of two EIP-7702 delegates. Both record the compiler version; only one carries the metadata hash.

Checking the note

Today I asked Blockscout’s API whether each of the 59 has verified source, and asked Sourcify the same. Nine of the ten stripped contracts are verified on Blockscout. Of the 46 that kept the full hash, 21 are. The other three have no CBOR tail at all, and one of those is verified.

Counting authorizations instead of contracts widens the gap. In that September window, 369 of the 371 authorizations to stripped delegates went to a contract with published source. For the full-hash contracts it was 324 of 774, about 42%.

Two pairs of horizontal bars. By contract, 9 of 10 stripped delegates are verified against 21 of 46 with the full hash. By authorization, 369 of 371 to stripped delegates went to verified code against 324 of 774.
59 delegate contracts from one 200-block window in September, verification read on 27 October.

The one stripped contract with no source, 0xbaa66503..., drew two authorizations. The second most delegated contract in the whole sample, 0x67b743f3... with 144, carries a full IPFS hash and has no verified source on either service. Neither does 0x912518c2... with 96. Three unverified contracts of 2,140 bytes each carry the same IPFS hash, so they’re one build deployed three times.

Who strips it

The nine verified ones carry six contract names: Uniswap’s CaliburEntry at three addresses, Alchemy’s SemiModularAccount7702 at two, two TokenPocket delegators, UniversalGaslessDelegate, and the most delegated contract of all, with 308 of the window’s authorizations, whose verified source names it Poisoner.

A table of ten rows, one per stripped delegate, giving the address prefix, the verified contract name, the solc version read from the tail, authorizations in the window, Blockscout status and Sourcify status. Nine rows are verified; none is an exact match on Sourcify.
The ten stripped delegates. Blockscout and Sourcify read 27 October; authorizations from the 8 September window.

Calibur’s foundry.toml and the one in Alchemy’s modular-account repository both set bytecode_hash = "none" as the project default. Neither file says why. Five of the ten addresses look mined: two start with eight zeros, one with 0x0000fb7702, one with 0x69007702, and one starts 0x77021100 and ends 0000. The metadata hash changes whenever a comment or a file path changes, even when the compiled code doesn’t, and an address found by searching CREATE2 salts depends on the exact bytecode. Dropping the hash ties the bytecode to the code alone. That’s my reading of the reason, not something either project says.

What Sourcify makes of them

Sourcify answered for eight of the stripped contracts, and all eight came back as match, none as exact_match. Of the full-hash contracts it has, 13 of 17 are exact. Sourcify’s docs say an exact match rests on the metadata hash in the deployed code agreeing with the recompiled one, which is how it proves the source files are byte for byte the ones the author compiled. With no hash in the bytecode there’s nothing for that check to hold on to, so I’d expect every stripped contract to stop at match, whoever wrote it.

What this doesn’t show

The authorization counts come from one 200-block window in September, so they say which delegates were busy then. Verified means Blockscout’s flag and Sourcify’s record as I read them on 27 October. I didn’t check Etherscan, which wants an API key, and a contract could be verified there and nowhere else. I also tried two public IPFS gateways for the hashes on the unverified contracts and both refused the requests, so I can’t say whether any of those metadata files exist somewhere.


Ten EIP-7702 delegates strip their metadata hash. Nine of them publish their source was originally published in Coinmonks on Medium, where people are continuing the conversation by highlighting and responding to this story.

Before yesterdayCoinmonks

BDK’s Electrum txid check took 171 days to ship, and 5 of the 9 crates using it can’t get it

19 October 2026 at 02:24

The fix is one comparison. Only bdk_electrum 0.24.1 has it, there’s no fixed 0.23, and LDK Node shipped on 0.23 eight hours later

In BDK’s Electrum client, the loop that gathers fee data for a wallet has a comment on line 588 of the 0.23.2 source that says exactly what it should do:

let prev_tx = self.fetch_tx(outpoint.txid)?;
// Ensure server returns the expected txout.
let txout = prev_tx
.output
.get(vout as usize)

The only check under that comment is that output number vout exists. Nothing checks that prev_tx is the transaction the wallet asked for. fetch_tx sends a txid to the Electrum server, takes back whatever transaction the server returns and caches it under the txid it asked for. The output then goes into a map keyed by the outpoint the wallet asked about, and that map is where BDK gets input values when it works out the fee on a transaction whose inputs the wallet doesn’t own.

So for a parent the wallet doesn’t already hold, a server could answer with any transaction that has enough outputs, and its amount would become that input’s value in the wallet’s fee numbers. That’s as far as I can follow it in the code. fetch_tx also serves the wallet’s own history, and I haven’t traced what a swapped transaction does on that path, or run any of this against a server that lies.

Code from the published crates. Left, bdk_electrum 0.23.2: fetch_tx at line 71 asks the server for a txid and caches whatever comes back under that txid; fetch_prev_txout at line 588, under the comment Ensure server returns the expected txout, only checks that the output index exists, then stores the output under the outpoint the wallet asked about, which feeds fee calculation. Right, 0.24.1 lines 85 to 90: compute the txid of the returned transaction and return an error if it differs.
bdk_electrum 0.23.2 against 0.24.1: where the server’s answer was trusted, and the check that 0.24.1 adds.

One comparison, merged in April

The fix is six lines in fetch_tx: compute the txid of what came back and return an error if it isn’t the one requested. It was merged to master on 24 April as PR 2188.

Master isn’t what gets published, though. bdk_electrum 0.24.0 came out on 8 May without the check; I diffed the published sources, and its fetch_tx is the same as 0.23.2’s. The backport, PR 2228, was merged on 16 June into a branch called release/chain-0.23.x, and that name nearly caught me out. I read it as the 0.23 line getting the fix. It’s the release branch for bdk_chain 0.23, and bdk_electrum’s 0.24 releases come off it. The fix sat merged there for 118 days, until 0.24.1 was published on 12 October at 05:29 UTC, 171 days after it first landed.

Timeline from April to October 2026. 24 April, PR 2188 merged to master. 8 May, bdk_electrum 0.24.0 published without the check. 16 June, PR 2228 backported to release/chain-0.23.x. 12 October 05:29 UTC, 0.24.1 published with the check. 12 October 13:53 UTC, LDK Node 0.7.1 published, still requiring bdk_electrum 0.23. No 0.23 release after 0.23.2 of September 2025.
From merge to release: 171 days, 118 of them merged on the branch that 0.24.1 was cut from.

Who can’t take it

Cargo treats 0.23 and 0.24 as incompatible, the way it treats any minor bump before 1.0. A crate that asks for bdk_electrum 0.23 gets 0.23.2 at most, and no 0.23 release has the check. Moving to 0.24 also moves electrum-client from 0.24 to 0.25, so it takes an edit to the manifest, and cargo update won’t do it.

crates.io lists 11 crates that depend on bdk_electrum. Two use it only in tests. Of the other nine, five ask for 0.23 and four for 0.24. LDK Node is one of the five. Its 0.7.1 release came out at 13:53 UTC on 12 October, eight hours and 24 minutes after 0.24.1, still on bdk_electrum 0.23, and it asks for previous outputs in both its sync and its full scan. It does fill the client’s cache from its own wallet first, so parents it already holds never go to the server. fedimint-ldk-node 0.7.0 asks for 0.23 too. LDK Node’s main branch has moved to 0.24.0, which resolves to 0.24.1 on a fresh lockfile, so its next release should carry the check.

A table of crates.io dependents, latest version of each. Asking for 0.23, which has no fixed release: ldk-node 0.7.1, fedimint-ldk-node 0.7.0, emvault-electrum 0.10.0, Hash_Pool and zk_coinjoin_lib. Asking for 0.24, which resolves to 0.24.1: bdk-cli 4.0.0, cdk-bdk 0.18.2, agent-first-pay 0.11.0 and hledger-btc-core 0.1.0. Two more, bdk_wallet and bdk-reserves, use it only in tests.
The nine crates on crates.io that depend on bdk_electrum outside tests, by the version they ask for.

Downloads tell it from the other side. On 13 October, the first full day 0.24.1 existed, crates.io counted 2,050 downloads of bdk_electrum, and 530 were 0.24.1. 799 were 0.23.2, the last of the line that has no fix. Downloads are CI runs and mirrors as much as people, so I wouldn’t read them as users, but they’re the only count there is.

Stacked daily bars from 30 September to 14 October 2026. Before 12 October every download is a version without the check. On 13 October, 530 of 2,050 downloads were 0.24.1 and 1,520 were older versions, 799 of them 0.23.2. 14 October is a partial day.
Daily crates.io downloads of bdk_electrum, split by whether the version has the txid check. 14 October is partial.

The case where any of this matters is a node pointed at an Electrum server somebody else runs. I don’t know of a way to count LDK Node deployments set up like that. Until LDK Node ships a release on 0.24, the apps built on it can’t get the check without patching LDK Node itself.


BDK’s Electrum txid check took 171 days to ship, and 5 of the 9 crates using it can’t get it was originally published in Coinmonks on Medium, where people are continuing the conversation by highlighting and responding to this story.

❌
❌