HTLC-Zeitverriegelungen im Design von Cross-Chain-Swaps: Eine STONfi/Omniston-Fallstudie
Jeder Cross-Chain-Swap muss eine unangenehme Frage beantworten: Was hindert eine Seite daran, das Geld zu nehmen und einfach abzuhauen? Es gibt keinen Schiedsrichter zwischen zwei Blockchains. Ethereum weiß nicht, was auf TON passiert ist, und TON weiß nicht, was auf Base passiert ist. Daher muss das Protokoll einen Schiedsrichter aus Bausteinen zusammenbauen, die beide Ketten bereits verstehen: einen Hash, ein Geheimnis und eine Uhr.
Die Uhr ist der Teil, den viele überspringen. Hashlocks ziehen Aufmerksamkeit auf sich, weil sie das clevere Element sind, aber die Timelock entscheidet darüber, was passiert, wenn etwas schiefgeht – und im Cross-Chain-Design ist „etwas geht schief“ genau das Lastenheft. Dieser Artikel erklärt in einfacher Sprache, wie Timelocks bei HTLC-basierten Swaps funktionieren, warum die beiden Uhren in einem Swap niemals gleich sein dürfen und wie sich das alles in Omniston widerspiegelt, der resolverbasierten Ausführungsschicht hinter STONfi-Swaps über verschiedene Chains hinweg.
Eine ehrliche Einschränkung, bevor wir starten. Der STONfi-Text, auf den ich hier aufbaue, beschreibt die Struktur von Omnistons Swaps (verknüpfte HTLCs, Resolver, Refund-Pfade), veröffentlicht aber keine exakten Timeout-Dauern. Wo ich unten Zahlen verwende, sind sie nur illustrativ. Entscheidend ist die Argumentation, nicht die konkreten Werte.
🧩 Zwei Chains, kein Schiedsrichter
Vergiss den Fachjargon für einen Moment und stell dir einen Spind vor, an dessen Tür zwei Regeln stehen. Regel eins: Bis Freitag öffnet dieser Spind für jeden, der das Geheimwort bringt. Regel zwei: Nach Freitag öffnet er nur für die Person, die das Geld eingezahlt hat. Das ist ein HTLC, ein Hashed Timelock Contract. Der „gehasht“-Teil ist das Geheimwort (als Fingerabdruck gespeichert, sodass niemand es vom Spind ablesen kann). Der „timelock“-Teil ist Freitag.
Jetzt setze auf jede Chain einen dieser Spinde, beide mit demselben Geheimwort geschützt. Sobald jemand das Geheimnis nutzt, um einen Spind zu öffnen, wird das Geheimnis öffentlich – und damit auch im anderen Spind nutzbar. Ein Ereignis settled beide Seiten. Das ist der ganze Trick.
Omniston baut jeden Cross-Chain-Swap exakt aus diesem Paar. Laut der eigenen Beschreibung von STONfi gibt es ein bid HTLC auf der Quell-Chain, in dem die Assets des Nutzers gelockt werden, und ein ask HTLC auf der Ziel-Chain, in dem der Resolver seine eigenen Assets lockt – beides teilt sich genau einen Hashlock. Das Protokoll erzeugt das Geheimnis in deinem Namen, sodass du es nie selbst anfassen musst. Wenn du neugierig bist, wie dieser Lock „mechanisch“ aussieht, ist daran nichts Exotisches:
import { createHash, randomBytes } from "node:crypto"; const secret = randomBytes(32); // zunächst bekannt nur einer Partei const hashlock = createHash("sha256").update(secret).digest("hex"); // sicher zum Veröffentlichen // Jeder kann ein behauptetes Geheimnis gegen den Lock prüfen... const isValid = (candidate: Buffer) => createHash("sha256").update(candidate).digest("hex") === hashlock; // ...aber niemand kann rückwärts vom Hashlock auf das Geheimnis arbeiten.
Dieses Snippet steht nur für die Intuition hier, denn in Omniston macht das Protokoll diesen Schritt für dich. Wichtig ist jedoch das Ergebnis. STONfi beschreibt genau drei Arten, wie ein Swap enden kann:
Das Geheimnis wird offengelegt: Beide Parteien erhalten die Assets, die ihnen zugesagt wurden.
Der Resolver antwortet nicht: Der Nutzer wird per Timelock erstattet.
Das Geheimnis wird nie offengelegt: Der Resolver wird per Timelock erstattet.
Es gibt kein viertes Ende, bei dem jemand am Ende beide Seiten hält oder bei dem weder das eine noch das andere. Enden zwei und drei hängen von der Uhr ab – und genau deshalb existiert dieser Artikel.
⏳ Was ein Timelock ist – und was nicht
Hier ist die häufigste Verwechslung, die ich sehe: Leute lesen „timelock“ und nehmen an, es sei die Zeit, die der Swap dauert. Dem ist nicht so. Es ist die Deadline, nach der sich ein festhängender Swap selbst zurückwickelt. Im Normalfall wartet niemand überhaupt darauf. STONfi sagt, die meisten Cross-Chain-Swaps sind in etwa 15 bis 40 Sekunden durch, während ein Timelock eine viel längere Sicherheits-Deadline ist. Der Timelock ist der Worst Case, der als Zahl festgeschrieben wird – die Antwort auf „Wie lange könnten meine Gelder im Escrow stecken, wenn die andere Seite verschwindet?“
Jeder Lock hat zwei Zeitfenster. Vor dem Deadlines kann nur jemand, der das Geheimnis kennt, es einfordern. Nach der Deadline kann nur der ursprüngliche Besitzer die Gelder zurückholen. Diese Zeitfenster überlappen sich auf keiner einzelnen Chain. Hier ist eine vereinfachte Version dieser Logik in Solidity, der Sprache hinter den meisten EVM-Verträgen. Das ist eine Illustration, nicht Omnistons echte Contracts und kein Production-Code:
struct Lock { address sender; // wer es finanziert hat, bekommt die Refund-Adresse receiver; // wer mit dem Geheimnis Anspruch erheben kann uint256 amount; bytes32 hashlock; // sha256(secret) uint256 timelock; // Unix-Timestamp: die Deadline bool settled; } function claim(bytes32 id, bytes calldata secret) external { Lock storage l = locks[id]; require(!l.settled, "already settled"); require(block.timestamp < l.timelock, "too late to claim"); require(sha256(secret) == l.hashlock, "wrong secret"); l.settled = true; emit Claimed(id, secret); // das Geheimnis ist jetzt on-chain öffentlich _pay(l.receiver, l.amount); } function refund(bytes32 id) external { Lock storage l = locks[id]; require(!l.settled, "already settled"); require(block.timestamp >= l.timelock, "not expired yet"); l.settled = true; _pay(l.sender, l.amount); }
Schau dir die beiden Vergleichsoperatoren an. claim verlangt, dass die aktuelle Zeit vor der Deadline liegt, refund verlangt, dass sie zur Deadline hin oder danach liegt. In einem einzelnen Contract gibt es nie einen Moment, in dem beides funktioniert – genau das willst du. Und beachte die Zeile emit Claimed(id, secret): Ein Claim verschiebt nicht nur Geld, er veröffentlicht auch das Geheimnis. Diese eine Zeile verbindet die beiden Chains miteinander. Wenn der Nutzer auf der Zielseite claimt, wird das Geheimnis öffentlich, und der Resolver kann es auf der Quellseite nutzen.
Das Beste an Timelocks finde ich ihre Langeweile. Kein Oracle, kein Komitee, kein „vertraut uns“. Nur eine Zahl und ein Vergleich. In der Security-Design-Welt ist langweilig ein Kompliment.
🎯 Die Regel, die zählt: zwei Uhren, niemals gleich
Bisher gibt es pro Chain genau eine Uhr, und alles sieht ordentlich aus. Der subtile Teil ist, dass die beiden Uhren unterschiedlich gesetzt werden müssen, und der Unterschied ist kein Detail. Es ist die gesamte Sicherheitsbegründung.
Verfolge die Reihenfolge der Ereignisse in einem Omniston-Swap. Die Assets des Nutzers gehen in das bid HTLC auf der Quell-Chain. Ein Resolver reserviert die Order und locked dann seine eigenen Assets im ask HTLC auf der Ziel-Chain. Der Nutzer claimt das Ziel-Asset, wodurch das Geheimnis offengelegt wird. Nur dann kann der Resolver dieses Geheimnis verwenden, um das Quell-Asset aus dem bid HTLC einzufordern.
Schau dir an, wer als Letztes reagieren muss: der Resolver. Nachdem das Geheimnis auf der Ziel-Blockchain erscheint, braucht der Resolver genug Zeit, um es zu bemerken und eine Gutschrift auf der Quell-Blockchain bestätigen zu lassen. Wenn auf der Quellseite der Lock abläuft, bevor das passiert, kann der Nutzer die Assets auf der Quellseite zurückerstatten, während er bereits das Asset auf der Zielseite hält. So spielt sich dieser Fehlschlag ab, wenn die beiden Deadlines gleich sind oder in falscher Reihenfolge gesetzt wurden:
Die Assets des Nutzers liegen im bid HTLC auf der Quell-Chain und laufen bei Zeit T ab.
Die Assets des Resolvers liegen im ask HTLC auf der Ziel-Chain und laufen ebenfalls bei T ab.
Kurz vor T fordert der Nutzer die ask-Seite an, und das Geheimnis wird öffentlich.
Der Resolver sieht das und reicht einen Claim auf der bid-Seite ein, aber eine Überlastung verzögert das über T hinaus.
Das bid HTLC ist abgelaufen. Der Nutzer erstattet die Quell-Assets und behält das Ziel-Asset. Der Resolver landet am Ende bei keinem der beiden.
Die Lösung ist, dass der Timelock auf der Quellseite (bid) später abläuft als der Timelock auf der Zielseite (ask), und zwar mindestens um die Reaktionszeit des Resolvers. Diese Lücke ist der Schutz für denjenigen, der als Zweiter handelt. (Zur Klarstellung: Das ist eine Eigenschaft, die jeder korrekte Zwei-Party-HTLC-Swap braucht, nicht eine Behauptung über Omnistons konkrete Einstellungen.) Es gibt außerdem einen Nebeneffekt, den man beachten sollte: Wenn ein Swap sein Geheimnis nie offenbart, kommt die Rückerstattung des Nutzers erst, wenn der längere Lock auf der Quellseite abläuft. Dieselbe Lücke, die den Resolver schützt, bestimmt also auch deine schlechteste Wartezeit.
Hier ist eine Skizze, wie man diese Deadlines auswählen könnte. Auch hier: mein illustratives Modell, nicht Omnistons Implementierung:
interface ChainTiming { finalitySeconds: number; // wie lange, bis es sicher ist, auf das zu handeln, was ich gesehen habe worstInclusion: number; // bad-day delay, bis eine Transaktion tatsächlich ankommt } function pickTimelocks(source: ChainTiming, dest: ChainTiming, buffer = 900) { // Ask HTLC (destination): Der Nutzer braucht Zeit, um den Lock zu sehen und einzufordern const askWindow = dest.finalitySeconds + dest.worstInclusion + buffer; // Bid HTLC (source) muss länger leben als das ask HTLC – um die Reaktionszeit des Resolvers: // sieh das offenbarte Geheimnis auf dest, dann bring einen Claim auf source unter const reaction = dest.finalitySeconds + source.worstInclusion + buffer; return { askWindow, bidWindow: askWindow + reaction }; // bid strikt später, per Design }
🌐 Warum zwei Chains die Marge wirklich hart machen
Wenn beide Chains gleich schnell ticken würden, wäre das Dimensionieren dieser Lücke trivial. Tun sie aber nicht – und genau dort beginnt echte Ingenieursarbeit. Ein paar Dinge fließen in die Marge ein:
Finalität. Ethereums volle Finalität dauert ungefähr 12 bis 13 Minuten, während TON in Sekunden bestätigt. „Sicher, um auf das zu handeln, was ich gesehen habe“ bedeutet auf jeder Seite etwas ganz anderes.
Einschlussverzögerung am schlechten Tag. Fee-Spikes und Überlastung können eine Transaktion zurückdrängen. Die Marge muss die schlimmste plausible Verzögerung abdecken, nicht den Durchschnitt.
Timestamp-Verhalten. Contracts sehen Block-Timestamps, nicht deine Wall-Clock. Block-Zeit kann leicht von der echten Zeit abweichen, und die Marge muss das auffangen.
Ein Puffer für das Unerwartete. Ausfälle, Retries und ein Resolver, der einen zweiten Versuch braucht, fressen alle Zeit.
Führe die Skizze oben mit erfundenen Zahlen aus, und die Richtung des Swaps beginnt zu zählen. Angenommen, eine Ethereum-ähnliche Quell-Chain hat 780 Sekunden Finalität und eine 300-Sekunden Worst-Case-Einschlussverzögerung, während eine TON-ähnliche Ziel-Chain 10 Sekunden Finalität und 60 Sekunden Worst-Case-Einschluss hat. Mit einem 15-Minuten-Puffer ergibt sich für das ask-Fenster 970 Sekunden, für die Reaktionszeit 1.210 Sekunden und für den bid-Lock etwa 2.180 Sekunden – also ein Worst-Case-Escrow von ungefähr 36 Minuten. Drehe die Richtung: mit TON als Quell-Chain und der langsamen Chain als Ziel-Chain liefert dieselbe Funktion 1.980 Sekunden für die ask-Seite und 3.720 Sekunden (etwa 62 Minuten) für die bid-Seite. Gleicher Protokoll, gleicher Puffer, aber ein sehr unterschiedlicher Worst Case – rein weil welche Chain wie lange warten muss.
Das ist auch der Trade-off in einem Satz. Mach die Marge zu eng und derjenige, der als Zweiter handelt, wird zusammengedrückt. Mach sie zu großzügig und jeder fehlgeschlagene Swap hält Gelder länger im Escrow als nötig. Es gibt keine „kostenlose“ Einstellung, nur eine bewusste.
Immer wenn jemand sagt, ein Cross-Chain-Swap „dauert ein paar Sekunden“, will ich nach dem schlechten Tag fragen. Systeme werden an ihrem Worst Case gemessen – und bei HTLCs ist der Worst Case buchstäblich als Zahl in den Vertrag geschrieben.
🧭 Wie Omniston das zusammenfügt
Mit diesem Hintergrund liest sich die Form von Omnistons Design anders. So beschreibt STONfi das Ende-zu-Ende einer Swap-Abwicklung: Der Nutzer signiert eine Order mit eingebettetem Hashlock, und seine Assets werden auf der Quell-Chain gelockt. Ein Resolver reserviert die Order und lockt seine eigenen Assets auf der Ziel-Chain unter demselben Hashlock. Das Geheimnis wird on-chain offengelegt, der Nutzer fordert das Ziel-Asset ein, der Resolver fordert das Quell-Asset ein – und beide Seiten settlen aus einem einzigen Ereignis.
Drei Details stechen heraus, sobald man es durch die Timelock-Brille betrachtet.
Resolver haben Haut im Spiel. Ein Resolver verspricht nicht nur, zu liefern. Er locked seine eigenen Destination-seitigen Assets im ask HTLC, sobald er committet. Wenn der Swap scheitert, bekommt er diese Assets erst zurück, wenn sein Timelock abläuft – und er holt sich nichts von der Nutzerseite, solange das Geheimnis nicht vorliegt. Sein Kapital ist gebunden, bis das Ergebnis auf die eine oder andere Weise feststeht.
Teilfüllungen nutzen unabhängige Uhren. STONfi’s Beispiel ist ein Order-Settlement über 1.000 USDT als vier parallele 25%-Sub-Swaps, jeder mit eigenem Geheimnis, eigenem Hashlock und eigenem HTLC-Paar. Wenn drei fertig sind und einer nicht, sind die drei final und der vierte wird erstattet. Da jede Portion ihren eigenen Timelock trägt, kann eine festhängende Portion die anderen nicht als Geiseln halten. Kleinere unabhängige Einheiten bedeuten eine kleinere Explosionsradius, wenn etwas nicht wie erwartet funktioniert.
Manchmal startet die Uhr nie. Wenn kein Resolver eine Quote zurückgibt, wird nichts gelockt und kein Asset auf der Quellseite wird angefasst. Der Nutzer behält seine ursprünglichen Assets und kann später erneut versuchen – ohne dass etwas zurückzuholen ist, weil nichts fest eincommitet wurde.
Außerdem stellt sich die Frage, ob die Verträge, die all das erzwingen, überhaupt gut sind, und Vergleiche wie < vs. <= genau die Art von Sache sind, die leise schiefgehen. Laut STONfi’s Audit-Write-up hat TonTech jede Zeile der Omniston-Escrow-Contracts mit automatisierten und manuellen Checks geprüft und keine kritischen oder High-Severity-Probleme gefunden. Das ist ein positives Signal – aber es ist eine Prüfung eines spezifischen Scopes zu einem bestimmten Zeitpunkt, keine permanente Garantie.
Was ich an STONfi’s Ansatz besonders schätze, ist, dass die Fehlschlag-Story von Anfang an erzählt wird: drei Enden, kein viertes. Die meisten Cross-Chain-Produkte lassen dich danach suchen, was passiert, wenn etwas kaputtgeht.
✅ Was das für dich bedeutet
Für alle, die einfach den Swap nutzen, sind die Kernaussagen praktisch statt technisch:
Ein festhängender Swap hat ein begrenztes Ergebnis. Deine Gelder kommen gemäß Regel zurück, wenn der Timelock abläuft – nicht indem du ein Support-Ticket einreichst und hoffst.
Wenn ein Swap länger dauert als erwartet, prüfe vor allem den Status des bid HTLC auf einem TON-Explorer. STONfi rät ausdrücklich davon ab, erneut einzureichen, da doppelte Einreichungen nur das Bild vernebeln.
Halte ein kleines TON-Guthaben in der Quell-Wallet. In den STONfi-Notizen steht, dass das SDK für swap-bezogene Calls standardmäßig 0,3 TON verwendet, und eine Wallet nahe 0 kann eine Anfrage schon stoppen, bevor sie überhaupt Resolver erreicht.
Verifiziere vor dem Start die Ziel-Chain und die Token-Adresse. Eine Adresse, die auf einer Chain gültig ist, ist es nicht zwangsläufig auf einer anderen, und kein Timelock schützt dich davor, an den falschen Ort zu senden.
Für Builder, die Cross-Chain-Workflows integrieren, gelten drei Erkenntnisse aus dem Design. Mach die Timeout-Asymmetrie in deinem Code und deinen Tests explizit, weil „zufällig gleich“ genau der Bug ist, der jeden Happy-Path-Test durchrutschen lässt. Nutze Sicherheitsmargen aus dem Worst Case für jede Richtung, statt eine Zahl für beide zu verwenden. Und zeig Nutzern, was bei einem Fehlschlag passiert und ungefähr wann, denn ein Swap, der sich sicher zurückwickelt, sieht identisch aus wie ein kaputter, wenn die Oberfläche still bleibt.
Timelocks sind leicht zu übersehen, weil sie im Fall, dass alles funktioniert, überhaupt nichts tun. Sie existieren für den Tag, an dem etwas nicht funktioniert – und der Unterschied zwischen einem gut designten Paar und einem schlampigen zeigt sich genau dann. Omnistons Ansatz, zwei verknüpfte Locks, Resolver mit echtem Kapital, und jede mögliche Beendigung berücksichtigt, ist ein gutes Beispiel für ein Design, bei dem der langweilige Teil ernst genommen wurde.
❓ FAQ
Ist der Timelock dasselbe wie die Zeit, die mein Swap dauert? Nein. Es ist die Deadline für Refunds, falls etwas ins Stocken gerät. Die meisten Swaps sind in Sekunden fertig und kommen dem nie nahe.
Warum können beide Timelocks nicht einfach gleich sein? Weil derjenige, der als Zweiter claimt, Zeit braucht, um zu reagieren, nachdem das Geheimnis offengelegt wurde. Gleiche Deadlines können dazu führen, dass eine Seite beide Assets einsammeln kann, wenn ein Claim wegen Überlastung verspätet ankommt.
Was passiert, wenn kein Resolver meinen Swap quotet? Dann wird nichts gelockt, also gibt es nichts, was auslaufen könnte. Deine Assets bleiben dort, wo sie waren, und du kannst später erneut versuchen.
Veröffentlicht Omniston seine exakten Timelock-Dauern? Das STONfi-Material, das ich verwendet habe, beschreibt das Verfahren, aber nicht spezifische Dauern – daher sind alle Zahlen in diesem Artikel nur illustrativ. Für die aktuellen Parameter siehe STONfi’s Dokumentation.
$GRAM

