DEV Community

Baris Sozen
Baris Sozen

Posted on

One trade, many legs, zero gaps: how multi-leg atomicity actually works

Yesterday I wrote that rails are not settlement: a payment is one leg - agent pays a seller, one asset, one direction - and the agent-payment rails got very good at that very fast. This post is the other half of that argument. Because the moment your agent stops paying and starts trading, one leg is no longer enough, and the difference is not cosmetic. It is where money goes missing.

This is about a primitive we keep coming back to: multi-leg trade atomicity. It is unglamorous and it is the whole point.

A payment has one leg. A trade has at least two.

Picture the simplest real trade your agent might make at 3am with a counterparty it has never met: my USDC for your wETH. That is not one transfer. It is two:

  1. Leg A: my USDC moves to you.
  2. Leg B: your wETH moves to me.

Both must happen, or neither should. There is no acceptable world where leg A clears and leg B does not - that is just me sending you money and hoping.

Now make it realistic. The interesting agent trades are not two-leg. They are:

  • Cross-asset, cross-chain: my USDC on one chain for your asset on another. Now you have two transfers on two ledgers that do not share a clock.
  • Multi-party: A wants what B has, B wants what C has, C wants what A has. A three-way ring that only makes sense if all three legs fire together.
  • Bundled: "swap, then post the proceeds as collateral, then open the position" - a sequence where a partial fill leaves the agent in a worse state than not trading at all.

Every one of these has the same shape: N legs that are only correct as a set. The number of legs goes up; the requirement does not change. All of them clear, or all of them refund.

The gap is the bug

The naive implementation is a sequence. I send, I wait, you send. Between those two steps there is a window where one party has performed and the other has not. That window is the entire problem. It is where:

  • the counterparty simply walks after receiving leg A;
  • one chain reorgs or stalls and the legs desynchronize;
  • an agent crashes mid-sequence and leaves a half-finished trade nobody owns.

The standard "fix" you see shipped in agent stacks is to insert an escrow: a third party holds both sides until everything is ready, then releases. Read that carefully. It does not remove the gap. It relocates the gap into a custodian. Now the trust assumption is "this intermediary will release correctly and is still solvent and is not compromised." You have turned a counterparty-risk problem into a custodian-risk problem and called it settlement. It is orchestration wearing settlement's clothes.

Atomicity means there is no gap to relocate. Not a smaller gap. No gap.

How multi-leg atomicity binds the legs

The mechanism is a hashed timelock contract (HTLC), extended so that every leg of the trade hangs off the same secret.

Here is the core idea in one breath: pick a random secret s, compute its hash h = SHA256(s), and lock every leg of the trade under the condition "claimable by revealing the preimage of h, refundable after timeout T."

secret s ; h = SHA256(s)

leg A:  lock(amount_A -> counterparty, unlock_if = preimage(h), refund_after = T_A)
leg B:  lock(amount_B -> me,           unlock_if = preimage(h), refund_after = T_B)
...
leg N:  lock(amount_N -> ...,          unlock_if = preimage(h), refund_after = T_N)
Enter fullscreen mode Exit fullscreen mode

Now the trade has exactly two possible end states:

  • The secret is revealed. Revealing s to claim any one leg publishes s on-chain. Once s is public, every other leg becomes claimable by everyone who was owed one. One reveal cascades into full settlement. The whole trade clears.
  • The secret is never revealed. Every leg's timeout passes and every locked amount refunds to its original owner. The whole trade unwinds. Nobody is left half-settled.

There is no third state. That is what "atomic" has to mean: not "fast," not "in one block," but no partial outcome is reachable. The legs are not coordinated by a referee; they are bound by the fact that they all depend on the same piece of information becoming public.

The one piece that takes real care is the timeout ordering. Refund windows have to be staggered so that the party who reveals the secret can never be the one left exposed - the leg you have to claim last must have the longest safety margin. Get the ordering wrong and you reintroduce a gap through the back door. This is the part of the design worth formally verifying, and it is exactly where our timeout/refund paths get the most attention (Slither + Halmos + Echidna + Stryker + a runtime invariant monitor).

Why a one-way rail structurally cannot do this

This is the crux, and it is an architecture statement, not a marketing one. A payment rail moves value in one direction: from payer to payee. Its data model is a transfer. To express a two-sided trade on a one-way rail you have to bolt on a second transfer and then coordinate the two - and the only place to put that coordination is a trusted party who watches both and decides when to release.

The HTLC's data model is a conditional lock, not a transfer. Conditionality is native. Adding a leg does not add a coordinator; it adds another lock under the same hash. That is why multi-leg atomicity is something settlement primitives can express and payment rails can only approximate by reintroducing trust.

Rails and settlement are complementary - your agent might use a payment rail to pay for an API call and a settlement layer to swap the asset it earned. But they are different layers, and only one of them can make N legs clear as a set.

Where this is real today

Being precise about status, because precision is the brand:

  • Ethereum mainnet: live end-to-end. The V1 HTLC contracts are deployed and immutable.
  • Sui: contracts are deployed and CLI-tested; gateway wiring is in progress. Not live - I will not call it that.
  • Bitcoin: signet-validated, with mainnet pending.

The MCP server exposes the primitive to agents as tools (create_htlc, get_htlc, withdraw_htlc, refund_htlc, swap_quote, swap_execute), so an agent composes a multi-leg trade the same way it calls any other tool - via hashlock-tech/mcp (scoped on npm). The deeper mechanics and the volume methodology live at hashlock.markets/methodology. The formal write-up of the settlement model is on SSRN.

The takeaway

A payment is one leg, and the rails own that leg. A trade is several legs, and a trade is only correct when every leg clears or every leg refunds - with no reachable state in between and no custodian holding the bag in the middle. Multi-leg atomicity is the primitive that makes that true, and it is the property an autonomous agent needs before you let it trade for you while you sleep.

What is the most leg-heavy trade you would actually trust an agent to execute on its own - and what would have to be true for you to trust it? I read every reply.

Top comments (0)