Yesterday's post ended on a question we did not answer: what happens on the failure path when nobody is watching. Here is the concrete version of it, and it is the most common mistake we see in agent settlement designs.
An agent needs to end up holding asset C. It holds asset A. There is no direct A/C market with depth, so the route is A to B, then B to C. Two legs, possibly on two different chains, possibly with two different counterparties.
The obvious construction is to make each leg an atomic swap. Lock A against B under hash H1 with a timeout. Lock B against C under hash H2 with a timeout. Each leg is individually atomic in the strict sense: either both sides of that leg complete, or both refund. That is a real property and it is provable.
Compose the two, and the composition is not atomic.
The failure state that has no owner
Leg one settles. The agent now holds B. Leg two's counterparty goes quiet, or its chain congests, or it simply looks at the price and decides not to reveal. The timeout on leg two expires, leg two refunds cleanly, and the agent is left holding B.
Nothing was stolen. Every contract behaved exactly as specified. Both legs were atomic. And the agent is now holding an asset it never wanted, acquired at a price it did not choose, with no plan for exiting it, because "exit B" was never a state the strategy had.
For a human trader this is an annoying afternoon. For an agent running unattended it is a position that sits there compounding until someone notices, and the entire premise of the agent economy is that nobody is going to notice.
The bug is not in either contract. The bug is in the invariant. "Each leg is atomic" is not the property you need. The property you need, borrowed almost word for word from how payment channel networks state it, is that each intermediate step completes if and only if the next step completes, and the first step completes if and only if the last one does.
That is a property of the path, not of any hop on it.
The construction that actually gives you path atomicity
The fix is old and well specified, and it is worth saying plainly that the payment channel world worked this out long before anyone was routing agent trades. It has two parts.
One preimage, not two. Every leg on the path locks against the same hash H. There is exactly one secret x such that hash(x) = H, and it is held by the party at the end of the path. When that party claims the final leg, it must publish x on chain to do so. That publication is what makes the leg before it claimable, and so on backwards to the origin. The legs stop being independent contracts that happen to be adjacent. They become one lock with several latches.
Descending timelocks with a buffer. The timeouts are set in strictly descending order from the origin to the destination. The first leg has the longest timeout, the last leg the shortest. The minimum gap between an incoming lock's expiry and the outgoing lock it funds is the parameter Lightning calls cltv_expiry_delta, and its whole job is to guarantee that when a downstream party reveals the secret at the last possible moment, the upstream party still has time to use that secret to claim its own incoming lock before that one expires.
Get the ordering backwards and you build the worst possible object: a path where an intermediary can be claimed downstream while its upstream claim has already expired. It pays out and cannot collect. That is not a theoretical risk, it is the specific thing the descending order exists to prevent.
Settlement runs backwards along the path even though value runs forwards. That inversion is the part people get wrong on the whiteboard.
Three costs nobody budgets for
This construction works. It is also not free, and the honest version of the pitch includes what it costs.
1. Capital-time grows with path length, and the first leg pays the most. Every lock on the path immobilises capital for its full timeout, whether or not the trade completes. Because timeouts descend, the origin's capital is locked longest. With Lightning's common 40-block delta, a three-hop path stacks 120 blocks of buffer above whatever the final leg needs. Nobody is paid for that lockup in most designs, which means it is a cost the routing layer quietly externalises onto whoever starts the trade.
2. There is a hard ceiling, and cross-chain makes it much harder than single-chain. Lightning caps total route timelock at 2016 blocks, agreed at the 2018 Adelaide specification meeting. The number is not arbitrary: roughly 20 maximum hops times a 40-block delta, plus margin for implementations that used to default to 144. On Bitcoin, 2016 blocks is about two weeks. That is the budget.
Now change one thing and denominate the same parameter across chains. Forty blocks on Bitcoin is roughly 6.7 hours. Forty blocks on Ethereum is about eight minutes. Two thousand and sixteen blocks on Bitcoin is two weeks; on Ethereum it is under seven hours. The same block-denominated safety parameter means wildly different things depending on which chain the hop sits on.
So a cross-chain path cannot inherit Lightning's numbers. The delta has to be denominated in wall-clock safety margin per chain and then converted back into that chain's block units, with the conversion sized for that chain's worst realistic reorg and congestion behaviour, not its average block time. Getting this wrong does not produce a loud failure. It produces a path that is atomic in the happy case and quietly unsafe under congestion, which is precisely when it will be tested.
3. The shared hash leaks the path. Using one H across every hop is what buys atomicity, and it is also a fingerprint. Any two participants on the same path can see they are on the same path by comparing hashes. Malavolta et al. formalised the consequence at NDSS 2019 as the wormhole attack: two colluding non-adjacent participants recognise the shared hash, settle directly with each other, and skip every intermediary between them, who lose their fees while having had capital locked the whole time. The paper's own fix, anonymous multi-hop locks, keeps the linkage that atomicity requires while making each hop's lock cryptographically distinct.
If your multi-leg design routes through intermediaries who expect to be paid, this is not an academic footnote. It is the economics of your routing layer.
Why this gets worse with agents, not better
Every construction above has a residual: whoever holds the preimage holds an option. They can look at the price and decide whether to reveal. If it moved against them, they let the clock run out and take the refund. They paid nothing for that optionality, and the counterparty wrote it for free.
A human exercises that option occasionally, when the move is large enough to be worth the reputational cost. A machine evaluates it on every block, has no reputation to spend, and can be running the same play against a thousand counterparties in parallel. Free optionality that was tolerable at human decision latency is not obviously tolerable at machine decision latency. The mechanism did not change. The rate at which it gets exercised did.
This is why we keep arguing that the settlement layer for agents cannot be assembled out of payment primitives after the fact. A payment is one leg. A trade is two. A routed trade is more than two, and the composition rules are strictly harder than the rules for any single hop.
Where we are
We build the settlement layer for the agent economy: sealed-bid RFQ fused with HTLC atomic settlement, so two agents with no reason to trust each other can trade across chains without a bridge, a custodian or an arbiter. Settlement runs live end-to-end on Ethereum mainnet today. Our Sui contracts are deployed and CLI-tested with gateway wiring in progress, and they are not live. Bitcoin is signet-validated with mainnet pending. Base is next on the roadmap.
Multi-leg atomicity is a design we have specified and are building toward, not a shipped feature, and we would rather say that than imply otherwise. What is shipped is the single-leg case on Ethereum mainnet and the MCP surface that lets an agent drive it.
Protocol: https://hashlock.markets/about?utm_source=devto&utm_medium=article&utm_campaign=2026-08-31-multi-leg-atomicity
MCP server on npm: hashlock-tech/mcp (scoped), v0.6.0
Volume methodology: https://hashlock.markets/methodology?utm_source=devto&utm_medium=article&utm_campaign=2026-08-31-multi-leg-atomicity
Whitepaper: https://papers.ssrn.com/sol3/papers.cfm?abstract_id=6712722
The question I actually want answered: if you are routing multi-step trades or payments across chains today, how are you setting your timelock deltas? Fixed block counts per chain, wall-clock converted at current block time, or something adaptive to congestion? I have not found a good public answer to this and I suspect most implementations picked a number once and never revisited it.
Top comments (0)