<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Web3KD</title>
    <description>The latest articles on DEV Community by Web3KD (@web3kd).</description>
    <link>https://dev.to/web3kd</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4024978%2Fe4f1ed94-2c13-4e18-906e-5a59a0cdbe0a.png</url>
      <title>DEV Community: Web3KD</title>
      <link>https://dev.to/web3kd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/web3kd"/>
    <language>en</language>
    <item>
      <title>HTLC Timelocks in Cross-Chain Swap Design: A STONfi/Omniston Case Study</title>
      <dc:creator>Web3KD</dc:creator>
      <pubDate>Mon, 28 Sep 2026 12:29:22 +0000</pubDate>
      <link>https://dev.to/web3kd/htlc-timelocks-in-cross-chain-swap-design-a-stonfiomniston-case-study-4bc</link>
      <guid>https://dev.to/web3kd/htlc-timelocks-in-cross-chain-swap-design-a-stonfiomniston-case-study-4bc</guid>
      <description>&lt;h1&gt;
  
  
  HTLC Timelocks in Cross-Chain Swap Design: A STONfi/Omniston Case Study
&lt;/h1&gt;

&lt;p&gt;Every cross-chain swap has to answer one uncomfortable question: what stops one side from taking the money and walking away? There's no referee sitting between two blockchains. Ethereum doesn't know what happened on TON, and TON doesn't know what happened on Base. So the protocol has to build a referee out of pieces that both chains can already understand: a hash, a secret, and a clock.&lt;/p&gt;

&lt;p&gt;The clock is the part people skip. Hashlocks get the attention because they're the clever bit, but the timelock is what decides what happens when things go wrong, and in cross-chain design, things going wrong is the whole design brief. This article walks through how timelocks work in HTLC-based swaps in plain language, why the two clocks in a swap must never be equal, and how all of it shows up in Omniston, the resolver-based execution layer behind STONfi's cross-chain swaps.&lt;/p&gt;

&lt;p&gt;One honest caveat before we start. The STONfi write-up I'm drawing on describes the structure of Omniston's swaps (linked HTLCs, resolvers, refund paths) but doesn't publish exact timeout durations. Wherever I use numbers below, they're illustrative. The reasoning is what carries over, not the specific values.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧩 Two chains, no referee
&lt;/h2&gt;

&lt;p&gt;Forget the jargon for a second and picture a locker with two rules written on the door. Rule one: until Friday, this locker opens for anyone who brings the secret word. Rule two: after Friday, it opens only for the person who put the money in. That's an HTLC, a Hashed Timelock Contract. The "hashed" part is the secret word (stored as a fingerprint, so nobody can read it off the locker). The "timelock" part is Friday.&lt;/p&gt;

&lt;p&gt;Now put one of those lockers on each chain, both protected by the same secret word. The moment someone uses the secret to open one locker, the secret becomes public, and that makes it usable on the other locker too. One event settles both sides. That's the whole trick.&lt;/p&gt;

&lt;p&gt;Omniston builds every cross-chain swap out of exactly this pair. According to STONfi's own description, there's a &lt;strong&gt;bid HTLC&lt;/strong&gt; on the source chain, where the user's assets get locked, and an &lt;strong&gt;ask HTLC&lt;/strong&gt; on the destination chain, where the resolver locks its own assets, both sharing one hashlock. The protocol generates the secret on the user's behalf, so you never handle it yourself. If you're curious what that lock looks like mechanically, it's nothing exotic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;randomBytes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:crypto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;randomBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                                    &lt;span class="c1"&gt;// known to one party at first&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hashlock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// safe to publish&lt;/span&gt;

&lt;span class="c1"&gt;// Anyone can check a claimed secret against the lock...&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isValid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;hashlock&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ...but nobody can work backwards from the hashlock to the secret.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That snippet is only here for intuition, since in Omniston the protocol does this step for you. What matters is the outcome. STONfi describes exactly three ways a swap can end:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The secret is disclosed:&lt;/strong&gt; both parties receive the assets they were quoted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The resolver fails to respond:&lt;/strong&gt; the user is refunded by the timelock.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The secret is never disclosed:&lt;/strong&gt; the resolver is refunded by the timelock.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There's no fourth ending where someone is left holding both sides, or neither. Endings two and three are the ones that depend on the clock, and they're the reason this article exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⏳ What a timelock is, and what it isn't
&lt;/h2&gt;

&lt;p&gt;Here's the misunderstanding I see most often: people read "timelock" and assume it's how long the swap takes. It isn't. It's the deadline after which a stuck swap unwinds itself. In the normal case nobody waits for it at all. STONfi says most cross-chain swaps complete in roughly 15 to 40 seconds, while a timelock is a far longer safety deadline. The timelock is the worst case written down as a number, the answer to "how long could my funds sit in escrow if the other side vanishes?"&lt;/p&gt;

&lt;p&gt;Each lock has two windows. Before the deadline, only someone who knows the secret can claim. After the deadline, only the original owner can take the funds back. Those windows never overlap on a single chain. Here's a simplified version of that logic in Solidity, the language behind most EVM contracts. It's an illustration, not Omniston's actual contracts and not production code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct Lock {
    address sender;     // who funded it, gets the refund
    address receiver;   // who can claim with the secret
    uint256 amount;
    bytes32 hashlock;   // sha256(secret)
    uint256 timelock;   // unix timestamp: the deadline
    bool    settled;
}

function claim(bytes32 id, bytes calldata secret) external {
    Lock storage l = locks[id];
    require(!l.settled,                    "already settled");
    require(block.timestamp &amp;lt; l.timelock,  "too late to claim");
    require(sha256(secret) == l.hashlock,  "wrong secret");
    l.settled = true;
    emit Claimed(id, secret);              // the secret is now public on-chain
    _pay(l.receiver, l.amount);
}

function refund(bytes32 id) external {
    Lock storage l = locks[id];
    require(!l.settled,                    "already settled");
    require(block.timestamp &amp;gt;= l.timelock, "not expired yet");
    l.settled = true;
    _pay(l.sender, l.amount);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at the two comparison operators. &lt;code&gt;claim&lt;/code&gt; demands the current time be &lt;em&gt;before&lt;/em&gt; the deadline, &lt;code&gt;refund&lt;/code&gt; demands it be &lt;em&gt;at or after&lt;/em&gt; it. On a single contract there's never a moment when both work, which is exactly what you want. And notice the &lt;code&gt;emit Claimed(id, secret)&lt;/code&gt; line: claiming doesn't just move money, it publishes the secret. That one line is what links the two chains together. When the user claims on the destination side, the secret goes public, and the resolver can use it on the source side.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The thing I like most about timelocks is how boring they are. No oracle, no committee, no "trust us." Just a number and a comparison. In security design, boring is a compliment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  🎯 The rule that matters: two clocks, never equal
&lt;/h2&gt;

&lt;p&gt;So far there's one clock per chain, and it all looks tidy. The subtle part is that the two clocks have to be set &lt;em&gt;differently&lt;/em&gt;, and the difference isn't a detail. It's the entire safety argument.&lt;/p&gt;

&lt;p&gt;Trace the order of events in an Omniston swap. The user's assets go into the bid HTLC on the source chain. A resolver reserves the order, then locks its own assets in the ask HTLC on the destination chain. The user claims the destination asset, which reveals the secret. Only then can the resolver use that secret to claim the source asset from the bid HTLC.&lt;/p&gt;

&lt;p&gt;Look at who has to move last: the resolver. After the secret appears on the destination chain, the resolver needs enough time to notice it and get a claim confirmed on the source chain. If the source-side lock expires before that happens, the user can refund the source assets while already holding the destination asset. Here's how that failure plays out if the two deadlines are equal, or set in the wrong order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The user's assets sit in the bid HTLC on the source chain, expiring at time T.&lt;/li&gt;
&lt;li&gt;The resolver's assets sit in the ask HTLC on the destination chain, also expiring at T.&lt;/li&gt;
&lt;li&gt;Just before T, the user claims the ask side, and the secret becomes public.&lt;/li&gt;
&lt;li&gt;The resolver sees it and submits a claim on the bid side, but congestion delays it past T.&lt;/li&gt;
&lt;li&gt;The bid HTLC has expired. The user refunds the source assets and keeps the destination asset. The resolver ends up with neither.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The fix is to make the source-side (bid) timelock expire &lt;em&gt;later&lt;/em&gt; than the destination-side (ask) timelock, by at least the resolver's reaction time. That gap is the protection for whoever acts second. (To be clear, this is a property any correct two-party HTLC swap needs, not a claim about Omniston's specific settings.) There's a side effect worth noticing, too: if a swap never gets its secret revealed, the user's refund arrives when the longer, source-side lock expires. So the same gap that keeps the resolver safe also shapes your worst-case wait.&lt;/p&gt;

&lt;p&gt;Here's a sketch of how you might pick those deadlines. Again, this is my illustrative model, not Omniston's implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ChainTiming&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;finalitySeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// how long until it's safe to act on what you saw&lt;/span&gt;
  &lt;span class="nl"&gt;worstInclusion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// bad-day delay before a transaction actually lands&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;pickTimelocks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ChainTiming&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ChainTiming&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Ask HTLC (destination): the user needs time to see the lock and claim it.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;askWindow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;finalitySeconds&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;worstInclusion&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Bid HTLC (source) must outlive the ask HTLC by the resolver's reaction time:&lt;/span&gt;
  &lt;span class="c1"&gt;// see the revealed secret on dest, then land a claim on source.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reaction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;finalitySeconds&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;worstInclusion&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;askWindow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;bidWindow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;askWindow&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;reaction&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;  &lt;span class="c1"&gt;// bid strictly later, by design&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🌐 Why two chains make the margin genuinely hard
&lt;/h2&gt;

&lt;p&gt;If both chains ticked at the same speed, sizing that gap would be trivial. They don't, and that's where real engineering starts. A few things feed into the margin:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Finality.&lt;/strong&gt; Ethereum's full finality takes roughly 12 to 13 minutes, while TON confirms in seconds. "Safe to act on what I saw" means very different things on each side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inclusion delay on a bad day.&lt;/strong&gt; Fee spikes and congestion can push a transaction back. The margin has to cover the worst plausible delay, not the average one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timestamp behavior.&lt;/strong&gt; Contracts see block timestamps, not your wall clock. Block time can drift slightly from real time, and the margin must absorb that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A buffer for the unexpected.&lt;/strong&gt; Outages, retries, and a resolver that needs a second attempt all eat time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run the sketch above with made-up numbers and the direction of the swap starts to matter. Say an Ethereum-like source chain has 780 seconds of finality and a 300-second worst-case inclusion delay, while a TON-like destination has 10 seconds of finality and 60 seconds of worst-case inclusion. With a 15-minute buffer, the ask window comes out to 970 seconds, the reaction time to 1,210 seconds, and the bid lock to about 2,180 seconds, so a worst-case escrow of around 36 minutes. Flip the direction, with TON as the source and the slow chain as the destination, and the same function returns 1,980 seconds for the ask side and 3,720 seconds (about 62 minutes) for the bid side. Same protocol, same buffer, very different worst case, purely because of which chain has to wait for which.&lt;/p&gt;

&lt;p&gt;That's also the trade-off in one sentence. Make the margin too tight and the party acting second gets squeezed. Make it too generous and every failed swap holds funds in escrow longer than it needs to. There's no free setting, only a deliberate one.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Whenever someone says a cross-chain swap "takes a few seconds," I want to ask about the bad day. Systems get judged by their worst case, and for HTLCs the worst case is literally written into the contract as a number.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  🧭 How Omniston puts this together
&lt;/h2&gt;

&lt;p&gt;With that background, the shape of Omniston's design reads differently. Here's how STONfi describes a swap end to end: the user signs an order with the hashlock embedded and their assets get locked on the source chain. A resolver reserves the order and locks its own assets on the destination chain under the same hashlock. The secret is revealed on-chain, the user claims the destination asset, the resolver claims the source asset, and both sides settle from one event.&lt;/p&gt;

&lt;p&gt;Three details stand out once you're looking at it through the timelock lens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resolvers have skin in the game.&lt;/strong&gt; A resolver doesn't just promise to deliver. It locks its own destination-side assets in the ask HTLC the moment it commits. If the swap fails, it gets those assets back only when its timelock expires, and it doesn't collect anything from the user's side without the secret. Its capital is tied up until the outcome is settled one way or the other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Partial fills use independent clocks.&lt;/strong&gt; STONfi's example is a 1,000 USDT order settling as four parallel 25% sub-swaps, each with its own secret, hashlock, and HTLC pair. If three complete and one doesn't, the three are final and the fourth refunds. Since each slice carries its own timelock, a stuck slice can't hold the others hostage. Smaller independent units mean a smaller blast radius when something misbehaves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sometimes the clock never starts.&lt;/strong&gt; If no resolver returns a quote, nothing gets locked and no source-side asset is touched. The user keeps their original assets and can retry later, with nothing to recover because nothing was committed.&lt;/p&gt;

&lt;p&gt;There's also the question of whether the contracts enforcing all this are any good, and comparisons like &lt;code&gt;&amp;lt;&lt;/code&gt; versus &lt;code&gt;&amp;lt;=&lt;/code&gt; are exactly the sort of thing that go wrong quietly. According to STONfi's audit write-up, TonTech reviewed every line of Omniston's escrow contracts using automated and manual checks and found no critical or high-severity issues. That's a positive signal, though it's a review of a specific scope at a specific time, not a permanent guarantee.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What I appreciate in STONfi's approach is that the failure story gets told up front: three endings, no fourth. Most cross-chain products make you go digging for what happens when something breaks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  ✅ What this means for you
&lt;/h2&gt;

&lt;p&gt;For people just using the swap, the takeaways are practical rather than technical:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A stuck swap has a bounded outcome. Your funds come back by rule, when the timelock expires, not by filing a support ticket and hoping.&lt;/li&gt;
&lt;li&gt;If a swap takes longer than expected, check the bid HTLC's status on a TON explorer before doing anything else. STONfi specifically advises against resubmitting, since duplicate submissions just muddy the picture.&lt;/li&gt;
&lt;li&gt;Keep a small TON balance in the source wallet. The STONfi post notes the SDK defaults to 0.3 TON for swap-related calls, and a wallet near zero can stall a request before it even reaches resolvers.&lt;/li&gt;
&lt;li&gt;Verify the destination chain and token address before initiating. An address that's valid on one chain isn't valid on another, and no timelock protects you from sending to the wrong place.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For builders integrating cross-chain flows, three lessons carry over from the design. Make the timeout asymmetry explicit in your code and your tests, because "equal by accident" is precisely the bug that passes every happy-path test. Size margins from the worst case for each direction rather than borrowing one number for both. And show users what happens on failure and roughly when, since a swap that's safely unwinding looks identical to a broken one if the interface stays silent.&lt;/p&gt;

&lt;p&gt;Timelocks are easy to overlook because when everything works, they do nothing at all. They exist for the day something doesn't, and the difference between a well-designed pair and a sloppy one only appears then. Omniston's approach, two linked locks, resolvers with real capital committed, and every ending accounted for, is a good example of a design where the boring part was taken seriously.&lt;/p&gt;

&lt;h2&gt;
  
  
  ❓ FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is the timelock the same as how long my swap takes?&lt;/strong&gt;&lt;br&gt;
No. It's the deadline for refunds if something stalls. Most swaps finish in seconds and never come near it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why can't both timelocks simply be equal?&lt;/strong&gt;&lt;br&gt;
Because the party who claims second needs time to react after the secret is revealed. Equal deadlines can let one side collect both assets when congestion delays a claim.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens if no resolver quotes my swap?&lt;/strong&gt;&lt;br&gt;
Nothing gets locked, so there's nothing to time out. Your assets stay where they were and you can retry later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Omniston publish its exact timelock durations?&lt;/strong&gt;&lt;br&gt;
The STONfi material I used describes the mechanism but not specific durations, so any figures in this article are illustrative. For current parameters, check STONfi's documentation.&lt;/p&gt;




&lt;h2&gt;
  
  
  📚 Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.ston.fi/omniston-explained-how-cross-chain-swaps-on-ton-work-without-a-bridge/" rel="noopener noreferrer"&gt;STONfi Blog: Omniston explained, how cross-chain swaps on TON work without a bridge&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.ston.fi/cross-chain-swaps-and-bridging-whats-the-difference/" rel="noopener noreferrer"&gt;STONfi Blog: Cross-chain swaps and bridging, what's the difference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.ston.fi/omniston-escrow-contracts-audited/" rel="noopener noreferrer"&gt;STONfi Blog: Omniston escrow contracts audited&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.ston.fi/developer-section/omniston" rel="noopener noreferrer"&gt;STONfi Documentation: Omniston&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://t.me/s/stonfidex/1511" rel="noopener noreferrer"&gt;STONfi Official Telegram: cross-chain swaps, supported networks and limits&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Code samples are simplified illustrations, not Omniston's contracts. Not financial advice, DYOR. Disclosure: Official STONfi Ambassador.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>A Production Architecture for a STONfi-Powered Swap App: Front End, Quote Layer, Wallet, Indexer, and Monitoring</title>
      <dc:creator>Web3KD</dc:creator>
      <pubDate>Mon, 28 Sep 2026 12:02:51 +0000</pubDate>
      <link>https://dev.to/web3kd/a-production-architecture-for-a-stonfi-powered-swap-app-front-end-quote-layer-wallet-indexer-4l16</link>
      <guid>https://dev.to/web3kd/a-production-architecture-for-a-stonfi-powered-swap-app-front-end-quote-layer-wallet-indexer-4l16</guid>
      <description>&lt;h1&gt;
  
  
  A Production Architecture for a STONfi-Powered Swap App: Front End, Quote Layer, Wallet, Indexer, and Monitoring
&lt;/h1&gt;

&lt;p&gt;Getting a swap to work on your laptop takes an afternoon. STONfi's docs are good, the SDKs handle a lot, and there's even a public demo app you can read line by line. Getting that swap to keep working for real users, on a bad network day, with support tickets coming in, is a different job, and almost none of it is about the swap itself.&lt;/p&gt;

&lt;p&gt;This article is about that second job. It walks through the five layers I'd put around a swap built on STONfi and Omniston: the front end, the quote layer, the wallet, the indexer, and monitoring. For each one I'll say what it's for, what breaks if you skip it, and where STONfi ends and your own work begins. I'll keep the language plain and define the jargon as it shows up.&lt;/p&gt;

&lt;p&gt;Two honest notes first. If all you need is a swap button, you probably shouldn't build any of this: STONfi offers a ready-made swap widget, and that's a perfectly good answer. And the code below is a sketch, not a drop-in. It follows the names in the current Omniston docs, which introduced a more explicit API model with native cross-chain support in &lt;code&gt;v1beta8&lt;/code&gt;, and the SDK's event shapes have changed between versions. Pin your version and check the types you actually have installed.&lt;/p&gt;

&lt;h2&gt;
  
  
  🗺️ The big picture: who owns what
&lt;/h2&gt;

&lt;p&gt;Start with the split of responsibilities, because everything else follows from it. STONfi and Omniston give you liquidity, routing, quotes, and execution: the part where a request for the best price gets answered by competing sources and settled on-chain. You don't build that. What you build is everything that makes it usable and trustworthy inside your product.&lt;/p&gt;

&lt;p&gt;Follow a single swap through the system and the five layers appear in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Front end:&lt;/strong&gt; the user picks assets and an amount and sees what they'd get.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quote layer:&lt;/strong&gt; a live stream of quotes gets turned into something stable enough to show and to decide on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wallet:&lt;/strong&gt; the user reviews the exact transaction and signs it with their own keys.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tracking and indexer:&lt;/strong&gt; the app follows the swap to its end and keeps a durable record of what happened.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring:&lt;/strong&gt; you find out something is wrong before your users tell you.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feapcchhkbvs3zpap06lw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feapcchhkbvs3zpap06lw.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One rule sits over all five: your servers never hold keys or funds. The wallet signs, the chain settles, and everything you build is a window onto that, not a replacement for it. That's what non-custodial means for your architecture, and it's also why so much of the work below is about &lt;em&gt;records&lt;/em&gt; and &lt;em&gt;visibility&lt;/em&gt; rather than moving money.&lt;/p&gt;

&lt;p&gt;Omniston offers three ways in. The SDKs (Node.js and React) are the recommended route and handle the WebSocket connection, quote streaming, transaction building, and error handling for you. A low-level WebSocket JSON-RPC API exists for custom clients, and gRPC over TLS is the primary low-level option for backend integrations. For most teams the SDK is the right call, and the rest of this article assumes it.&lt;/p&gt;

&lt;h2&gt;
  
  
  🖥️ Front end and quote layer: quotes are a stream, not an answer
&lt;/h2&gt;

&lt;p&gt;The most common conceptual mistake in a first swap UI is treating a quote like the response to an ordinary API call: you ask, you get a number, you display it. Omniston works differently. You send a request for quote, and quotes keep arriving as resolvers and liquidity sources respond and update. The best one can change while the user is still looking at the screen. That's good for the price, and it's a design problem for the interface.&lt;/p&gt;

&lt;p&gt;So the front end needs a small layer in the middle that turns a moving stream into something a person can safely act on. I call it the quote layer, and its job is boring on purpose: take whatever the SDK emits, convert it into one internal shape, and stamp it with the moment &lt;em&gt;you&lt;/em&gt; received it. If the UI renders SDK objects directly, every SDK upgrade becomes a change to every component. If it renders your own &lt;code&gt;QuoteView&lt;/code&gt;, an upgrade touches one mapping function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Omniston&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useRfq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;QuoteRequest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@ston-fi/omniston-sdk-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Sandbox for development and CI. Production only for real traffic.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;OMNISTON_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wss://omni-ws.ston.fi&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wss://omni-ws-sandbox.ston.fi&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;omniston&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Omniston&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;apiUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OMNISTON_URL&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// The UI renders this, never the raw SDK object.&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;QuoteView&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;quoteId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;receivedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// when WE saw it, used for staleness checks&lt;/span&gt;
  &lt;span class="nl"&gt;estimatedOut&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// the number people hope for&lt;/span&gt;
  &lt;span class="nl"&gt;minimumOut&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// the number we can actually promise&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;QuotePanel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;QuoteRequest&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// useRfq streams events; quotes keep updating until you stop listening.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRfq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NoQuote&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"connection"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Loading&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Event shapes differ between SDK versions: check your installed types.&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$case&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;quoteUpdated&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;QuoteCard&lt;/span&gt; &lt;span class="na"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;toQuoteView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Any other event (for example, no quote available) is handled explicitly,&lt;/span&gt;
  &lt;span class="c1"&gt;// not treated as a crash.&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NoQuote&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"unavailable"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What should that layer actually be responsible for? A short list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Normalizing&lt;/strong&gt; the SDK's quote into your own shape, so upgrades stay local.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Freshness:&lt;/strong&gt; if no update has arrived for a set number of seconds, mark the quote stale and disable the confirm button rather than letting someone sign against a number that may have moved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guardrails:&lt;/strong&gt; warn or block when price impact crosses a threshold you choose, and enforce minimum amounts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logging what was shown:&lt;/strong&gt; quote ID, estimated and minimum output, timestamp. When a user later says "it told me I'd get more," this is the record that settles it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A kill switch:&lt;/strong&gt; a config flag that turns off a route, for example cross-chain, without a redeploy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to earn from the flow, this is also where referral fees live. Omniston lets you attach referral data to a quote request so a share of the swap fee goes to your address, configurable from 0.01% to 1% per swap and paid on-chain in the same transaction. There's a &lt;code&gt;flexible_referrer_fee&lt;/code&gt; option that lets the protocol lower your cut when that gets the user a better rate, which I think is the right default for an app that wants users to keep coming back.&lt;/p&gt;

&lt;h2&gt;
  
  
  👛 The wallet layer: where the user, not you, makes the decision
&lt;/h2&gt;

&lt;p&gt;The wallet layer is the smallest in code and the largest in consequences. On TON the usual path is TON Connect, with &lt;code&gt;@tonconnect/ui-react&lt;/code&gt; giving you the connect button and session handling. The flow has two separate stages, and your UI should respect that separation: connecting shares an address and lets your app &lt;em&gt;propose&lt;/em&gt; transactions, and signing is the only moment anything actually moves. Nothing you write should blur those two.&lt;/p&gt;

&lt;p&gt;A few things belong here that people tend to skip.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo4dymsv4d2smslt1qg48.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo4dymsv4d2smslt1qg48.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build the transaction from the quote you showed.&lt;/strong&gt; The React SDK provides build helpers for this, such as &lt;code&gt;useTonBuildSwap&lt;/code&gt;, so the transaction the wallet is asked to sign is derived from the exact quote on screen, not from a fresh request that might have a different price. Show the user the same numbers, especially the minimum they're guaranteed, right before the wallet prompt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Treat a rejected signature as a result, not an error.&lt;/strong&gt; The user looked at the terms and said no. That should return them to the swap screen with their inputs intact and no red banner. Genuine failures, like a network error or a transaction the chain rejected, deserve a different message and a different log entry, because you'll want to count them separately later.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I treat a rejected signature as the app working correctly. Someone read the terms and decided no. That's the best failure mode you can have, so don't dress it up as a crash.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Be strict about addresses.&lt;/strong&gt; For cross-chain swaps STONfi's flow only needs the source wallet connected, and offers an option to receive at a different destination address. If your app supports that, the destination is a manually entered value with no connected wallet to sanity-check it. Validate the format for the &lt;em&gt;destination&lt;/em&gt; chain (an address that's valid on one chain isn't valid on another), and display the full address back before signing. Omniston's newer API models source and destination chains as separate fields for exactly this reason, and your UI should keep them separate too.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔎 Tracking and the indexer: two clocks, two kinds of memory
&lt;/h2&gt;

&lt;p&gt;Once the user signs, two different needs appear, and they need different tools.&lt;/p&gt;

&lt;p&gt;The first is &lt;em&gt;live&lt;/em&gt;: the person is staring at your screen and wants to know what's happening right now. Omniston's SDK covers this with a tracking stream. You give it the quote ID, the trader's address, and an identifier for the outgoing transaction (the docs call it &lt;code&gt;outgoingTxQuery&lt;/code&gt;), and it emits events as the swap moves along. Model those events as a small state machine rather than a pile of booleans:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SwapStage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;awaiting-transfer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;in-progress&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;closed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;   &lt;span class="c1"&gt;// the stream ended: reconcile, don't assume success&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;watchSwap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;omniston&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;traderAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;outgoingTxQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onStage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;omniston&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;swapTrack&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;quoteId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quoteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;traderAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;outgoingTxQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// identifies the transaction the wallet just sent&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sub&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;$case&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;awaitingTransfer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;onStage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;awaiting-transfer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;progress&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;         &lt;span class="nf"&gt;onStage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;in-progress&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unsubscribed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="nf"&gt;onStage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;closed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="c1"&gt;// Persist every stage change to your own store here, not just in React state.&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the comment on &lt;code&gt;closed&lt;/code&gt;. A stream ending means &lt;em&gt;tracking&lt;/em&gt; ended, not that the swap succeeded. Treat it as a reason to check the outcome, never as good news.&lt;/p&gt;

&lt;p&gt;The second need is &lt;em&gt;durable&lt;/em&gt;, and this is where the indexer comes in. An indexer is just a service that reads chain data and keeps a searchable copy of the events you care about. You don't need to index the whole chain, only your own users' swaps and, if you use them, your referral earnings. Why bother? Because users close tabs, phones lose signal mid-swap, and support will eventually get the question "where is my swap?" React state can't answer it. A row in your database can.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;swap_attempts&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt;             &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;-- idempotency key, created when the user confirms&lt;/span&gt;
  &lt;span class="n"&gt;quote_id&lt;/span&gt;       &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;wallet_address&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;route_kind&lt;/span&gt;     &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;-- 'ton-swap' or 'cross-chain'&lt;/span&gt;
  &lt;span class="n"&gt;bid_asset&lt;/span&gt;      &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ask_asset&lt;/span&gt;      &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;bid_amount&lt;/span&gt;     &lt;span class="nb"&gt;NUMERIC&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;quoted_out&lt;/span&gt;     &lt;span class="nb"&gt;NUMERIC&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;minimum_out&lt;/span&gt;    &lt;span class="nb"&gt;NUMERIC&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;final_out&lt;/span&gt;      &lt;span class="nb"&gt;NUMERIC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                 &lt;span class="c1"&gt;-- filled during reconciliation, never from the UI&lt;/span&gt;
  &lt;span class="n"&gt;stage&lt;/span&gt;          &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;-- last stage we observed&lt;/span&gt;
  &lt;span class="n"&gt;sdk_version&lt;/span&gt;    &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt;     &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;reconciled_at&lt;/span&gt;  &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt;              &lt;span class="c1"&gt;-- set after checking against the source of truth&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two words in there deserve a plain explanation. &lt;em&gt;Idempotency&lt;/em&gt; means doing the same thing twice has the same effect as doing it once: the &lt;code&gt;id&lt;/code&gt; is created when the user confirms, so a double-tapped button creates one record, not two. &lt;em&gt;Reconciliation&lt;/em&gt; means comparing your records against a source of truth and fixing the differences. For finalized order history, reconciliation, and aggregate reporting, Omniston provides a dedicated History API, so a nightly job can walk your recent &lt;code&gt;swap_attempts&lt;/code&gt;, compare them against it and against the chain, and fill in &lt;code&gt;final_out&lt;/code&gt; with the real number. The UI's opinion of what happened is a guess. The reconciled value is the record.&lt;/p&gt;

&lt;p&gt;Cross-chain swaps add one more thing to track. As covered in the earlier piece on HTLC timelocks, a cross-chain swap that stalls unwinds itself at a deadline. So your records should distinguish &lt;em&gt;slow&lt;/em&gt; from &lt;em&gt;stuck&lt;/em&gt;, and your interface should tell a waiting user that their funds return by rule if the swap doesn't complete, rather than nudging them to try again.&lt;/p&gt;

&lt;h2&gt;
  
  
  📡 Monitoring: knowing before your users do
&lt;/h2&gt;

&lt;p&gt;The last layer is the one teams add after the first bad incident. I'd add it before. The goal isn't dashboards for their own sake; it's answering four questions quickly: is the quote stream healthy, are swaps completing, are failures spiking, and is anything stuck.&lt;/p&gt;

&lt;p&gt;Here's what I'd put alerts on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Quote stream health:&lt;/strong&gt; WebSocket disconnects, reconnect loops, and time-to-first-quote. If quotes stop arriving, your swap screen is quietly broken even though nothing crashed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Funnel drop-off:&lt;/strong&gt; quote shown, confirm clicked, signature approved, swap completed. A sudden drop at one step points straight at the cause.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failure taxonomy:&lt;/strong&gt; user rejections, wallet errors, chain-level failures, and swaps that never completed, counted separately. One blended "failure rate" hides every useful signal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Completion time tail:&lt;/strong&gt; the median matters less than the slowest few percent. A healthy swap finishes in seconds, so a growing tail is your early warning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stuck orders:&lt;/strong&gt; anything past the time you'd expect, especially cross-chain, checked against the refund deadline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SDK version drift:&lt;/strong&gt; run the sandbox in your CI so a dependency update that changes event shapes fails a test instead of a customer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fitpbzksvdwway1v0smkl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fitpbzksvdwway1v0smkl.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"No quote" is an answer, not an outage. Apps that flash a red error when no resolver responds train users to distrust the one moment the system is being honest with them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That distinction matters for how you build alerts too. On a thin pair at an odd hour, no quote is a normal market state, so show it calmly and don't page an engineer. Reserve the loud alarms for the things that indicate &lt;em&gt;your&lt;/em&gt; system is unhealthy. And keep a status page or at least a feature flag, so that if a route misbehaves you can switch it off for users deliberately instead of letting them discover it.&lt;/p&gt;

&lt;p&gt;A note on the front end itself, since it's the part attackers can actually reach. Pin your dependencies, use a strict content security policy, and never let your interface ask for a seed phrase or a signature the user hasn't been shown. A swap app that's perfectly architected behind the scenes but ships a compromised script is still a compromised swap app.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧭 What I'd build first, and what can wait
&lt;/h2&gt;

&lt;p&gt;If I were starting this week, I'd build in this order: the quote layer with its own &lt;code&gt;QuoteView&lt;/code&gt;, the wallet flow with clean separation of rejection versus failure, then the &lt;code&gt;swap_attempts&lt;/code&gt; table written from the first day, and only then the reconciliation job and alerts. Referral fees, custom destinations, and cross-chain routes can arrive later, one at a time, each behind a flag. The order reflects a simple idea: build the record of what happened before you build anything fancy on top of it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If I could build only one thing beyond the happy path, it would be the record of what the user was shown. Nearly every support question I can imagine starts with "what did it say?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;STONfi has done the hard part of this stack, the routing and the settlement. The architecture around it is where a swap becomes a product, and none of it is glamorous: normalize the quote, respect the signature, write things down, watch the tail. Get those four right and most of what goes wrong stays small and explainable.&lt;/p&gt;

&lt;h2&gt;
  
  
  ❓ FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Should the quote layer run in the browser or on my backend?&lt;/strong&gt;&lt;br&gt;
The browser with the React SDK is the simplest start and is how the public demo app works. A backend layer earns its place when you need shared logging, rate limiting, kill switches, or analytics, and you can add it later without changing the SDK contract.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need my own indexer, or is the History API enough?&lt;/strong&gt;&lt;br&gt;
The History API is the source for finalized order history and reconciliation, but you still want your own table of attempts, because it holds what the user was shown and where your app observed the swap, which no external source can tell you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I avoid duplicate swaps if a user double-taps confirm?&lt;/strong&gt;&lt;br&gt;
Create an idempotency key when the user confirms, use it as the record's ID, and ignore repeat submissions carrying the same key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use the sandbox for testing?&lt;/strong&gt;&lt;br&gt;
Yes, and you should for development and CI. The docs describe it as for development and testing only, so keep production traffic on the production endpoint.&lt;/p&gt;




&lt;h2&gt;
  
  
  📚 Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.ston.fi/developer-section/omniston" rel="noopener noreferrer"&gt;STONfi Documentation: Omniston Protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.ston.fi/developer-section/quickstart/omniston" rel="noopener noreferrer"&gt;STONfi Documentation: Omniston Guide (React)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.ston.fi/developer-section/omniston/sdk/react" rel="noopener noreferrer"&gt;STONfi Documentation: Omniston React SDK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.ston.fi/developer-section/omniston/referral-fees" rel="noopener noreferrer"&gt;STONfi Documentation: Referral Fees&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.ston.fi/developer-section/widget" rel="noopener noreferrer"&gt;STONfi Documentation: Swap Widget&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ston-fi/omniston-sdk" rel="noopener noreferrer"&gt;Omniston SDK on GitHub, including the changelog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://omniston.ston.fi" rel="noopener noreferrer"&gt;Omniston demo app&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Code samples are simplified sketches. Verify names and types against your installed SDK version. Not financial advice, DYOR. Disclosure: Official STONfi Ambassador.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Router Contracts, Pool Contracts, and Why Their Upgrade Models Differ</title>
      <dc:creator>Web3KD</dc:creator>
      <pubDate>Sun, 20 Sep 2026 10:37:00 +0000</pubDate>
      <link>https://dev.to/web3kd/router-contracts-pool-contracts-and-why-their-upgrade-models-differ-3dh3</link>
      <guid>https://dev.to/web3kd/router-contracts-pool-contracts-and-why-their-upgrade-models-differ-3dh3</guid>
      <description>&lt;h1&gt;
  
  
  &lt;strong&gt;Router Contracts, Pool Contracts, and Why Their Upgrade Models Differ&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;In most DeFi architectures, "which contracts are upgradeable" is a governance question. On STON.fi it's an architectural one — and the answer is unusually clean: the Router can be upgraded, and pools structurally cannot. Understanding &lt;em&gt;why&lt;/em&gt; that asymmetry exists, rather than just noting it, explains a surprising amount about how the entire DEX is designed to behave under change.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;🗨️&lt;/em&gt; "The router is the only contract that can be upgraded. Each Jetton that goes through the DEX is owned by the router. The router does not store anything about pairs." &lt;em&gt;— STON.fi, Architecture documentation&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔&lt;/p&gt;

&lt;h2&gt;
  
  
  🧩 Two Contracts, Two Completely Different Jobs
&lt;/h2&gt;

&lt;p&gt;The asymmetry starts with what each contract is actually responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;◆ &lt;strong&gt;Router&lt;/strong&gt; — the single entrypoint for every DEX call. It owns the Jetton wallets, receives &lt;code&gt;transfer_notification&lt;/code&gt; messages, decodes the custom payload, and forwards to the correct pool. It holds no pair-specific state at all.&lt;/li&gt;
&lt;li&gt;◇ &lt;strong&gt;Pool&lt;/strong&gt; — holds the AMM state for one specific pair: reserves, fee configuration, the actual pricing math. One pool per pair, each with its own address derived from its own state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That split is the whole story. The Router is &lt;em&gt;stateless with respect to pairs&lt;/em&gt;, which makes it safe to replace. A Pool is &lt;em&gt;nothing but&lt;/em&gt; pair state, which makes replacing it a fundamentally different and more dangerous operation.&lt;/p&gt;

&lt;p&gt;▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚙️ The Detail That Explains Everything: Pool Code Lives Inside Router State
&lt;/h2&gt;

&lt;p&gt;This is the architectural fact most people miss, and it reframes the entire question.&lt;/p&gt;

&lt;p&gt;Calling &lt;code&gt;getRouterData()&lt;/code&gt; returns the Router's state, and among its fields are &lt;code&gt;poolCode&lt;/code&gt;, &lt;code&gt;jettonLpWalletCode&lt;/code&gt;, and &lt;code&gt;lpAccountCode&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getRouterData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ContractProvider&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;isLocked&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;adminAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;tempUpgrade&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Cell&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// pending code/admin upgrade, zeros = none pending&lt;/span&gt;
  &lt;span class="nl"&gt;poolCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Cell&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;         &lt;span class="c1"&gt;// ← the Pool contract's code, stored in the Router&lt;/span&gt;
  &lt;span class="nl"&gt;jettonLpWalletCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Cell&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;lpAccountCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Cell&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pools don't carry their own upgrade mechanism because they don't need one in the usual sense. The Router holds the &lt;em&gt;code template&lt;/em&gt; from which pools are deployed. On TON, a contract's address is derived from its &lt;code&gt;StateInit&lt;/code&gt; — its code plus initial data — which means changing pool code doesn't mutate existing pools. It produces pools at &lt;em&gt;different addresses entirely&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;🗨️&lt;/em&gt; "Changing pool code doesn't upgrade a pool. It defines what the next pool will be."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Existing pools keep running the code they were deployed with, holding the liquidity that was deposited under those exact rules. That's not a limitation — it's a guarantee.&lt;/p&gt;

&lt;p&gt;▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔&lt;/p&gt;

&lt;h2&gt;
  
  
  🔐 The Router's Upgrade Path Is Deliberately Slow
&lt;/h2&gt;

&lt;p&gt;Being upgradeable doesn't mean being instantly mutable. STON.fi's Router upgrade flow is a two-phase commit with enforced timelocks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Initiate&lt;/strong&gt; — admin submits the new Router code. Nothing changes yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wait&lt;/strong&gt; — a minimum &lt;strong&gt;seven-day delay&lt;/strong&gt; before a code upgrade can be finalized. Admin changes carry a separate minimum &lt;strong&gt;two-day delay&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Finalize&lt;/strong&gt; — only once &lt;code&gt;finalize_upgrades&lt;/code&gt; is received does the new code take effect.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The pending state is publicly readable the entire time, through the &lt;code&gt;tempUpgrade&lt;/code&gt; field — zero values mean nothing is pending, non-zero means an upgrade is in flight and anyone can see it coming.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F68yycjsv4y258z2s1b9e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F68yycjsv4y258z2s1b9e.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;🗨️&lt;/em&gt; "Seven days isn't a delay for the team's benefit. It's a window for everyone else."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There's also &lt;code&gt;cancel_admin_upgrade&lt;/code&gt;, which exists specifically so a pending admin change can be revoked before it lands — an explicit escape hatch rather than a one-way door.&lt;/p&gt;

&lt;p&gt;▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔&lt;/p&gt;

&lt;h2&gt;
  
  
  🛑 What the Router Can Do &lt;em&gt;Without&lt;/em&gt; an Upgrade
&lt;/h2&gt;

&lt;p&gt;Not every change requires replacing code, and the distinction matters for understanding the actual trust surface:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;◆ &lt;strong&gt;&lt;code&gt;set_fees&lt;/code&gt; (&lt;code&gt;0x58274069&lt;/code&gt;)&lt;/strong&gt; — adjust a specific pool's liquidity, protocol, and referral fees. Fee ratios are expressed against a divider of 10000, so 1% is the value &lt;code&gt;100&lt;/code&gt;. Note the direction here: the &lt;em&gt;Router&lt;/em&gt; sends this message &lt;em&gt;to&lt;/em&gt; the Pool. Pools don't govern themselves.&lt;/li&gt;
&lt;li&gt;◇ &lt;strong&gt;Lock / unlock trading&lt;/strong&gt; — flipping &lt;code&gt;is_locked&lt;/code&gt; blocks &lt;code&gt;transfer_notification&lt;/code&gt; messages from being processed, which effectively halts all swaps and liquidity provision through that Router. A circuit breaker, no code change required.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a meaningful design choice. Routine parameter changes and emergency response both happen &lt;em&gt;without&lt;/em&gt; touching the timelocked upgrade path — which is exactly why that path can afford to be slow.&lt;/p&gt;

&lt;p&gt;▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔&lt;/p&gt;

&lt;h2&gt;
  
  
  📨 Why TON's Message Model Makes This Design Natural
&lt;/h2&gt;

&lt;p&gt;On a synchronous chain, a "router" is often just a helper library — a convenience wrapper around direct pool calls. On TON, asynchronous message passing makes it something structurally heavier.&lt;/p&gt;

&lt;p&gt;A jetton-to-jetton swap moves through a real chain of messages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User → their Jetton wallet
     → Router's Jetton wallet  (transfer_notification#7362d09c)
     → Router                  (decodes payload, op: swap 0x6664de2a)
     → Pool                    (executes AMM math against its own reserves)
     → LpAccount / payout      (settlement)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the Router genuinely owns the Jetton wallets for everything flowing through the DEX, it isn't optional infrastructure you could route around. Every swap and every liquidity provision physically passes through it. That's precisely why it's the contract worth making upgradeable — and precisely why that upgrade capability needs a seven-day window attached to it.&lt;/p&gt;

&lt;p&gt;There's also &lt;code&gt;cross_swap&lt;/code&gt; (&lt;code&gt;0x69cf1a5b&lt;/code&gt;), a payload op for chaining swaps on the same Router — multi-hop routing implemented as message chaining rather than as a nested synchronous call, which is simply what multi-hop has to look like in an async execution model.&lt;/p&gt;

&lt;p&gt;▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔&lt;/p&gt;

&lt;h2&gt;
  
  
  🔀 What This Means for Anyone Building On Top
&lt;/h2&gt;

&lt;p&gt;The practical consequence for integrators is direct, and STON.fi's own SDK documentation states it plainly: don't hardcode a router address.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Hardcoded — a Router upgrade silently invalidates this&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dexFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;EQB3ncy...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Let the API resolve the current router, every time&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;simulationResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;simulateSwap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dexFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;simulationResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's a second consequence worth naming: because pool code is a Router-held template and pool addresses derive from &lt;code&gt;StateInit&lt;/code&gt;, &lt;strong&gt;multiple pool types can coexist simultaneously&lt;/strong&gt;. v2 introduced CPI pools alongside the existing types, and the SDK migration notes are explicit that base Router and Pool method calls are deprecated in favor of specifying pool type explicitly. That's not an inconvenience — it's the visible surface of an architecture where new pool designs ship as &lt;em&gt;additions&lt;/em&gt;, never as forced migrations of existing liquidity.&lt;/p&gt;

&lt;p&gt;▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔&lt;/p&gt;

&lt;h2&gt;
  
  
  ✅ Why This Asymmetry Is the Right Call
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The stateless contract is the upgradeable one.&lt;/strong&gt; Routing logic can evolve without ever touching deposited liquidity, because the Router holds no pair state to corrupt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Immutable pools mean LPs face fixed rules.&lt;/strong&gt; Someone who deposits into a pool is trading against the exact code they deposited under — nobody can rewrite that pool's math underneath them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parameter changes and emergency stops bypass the timelock entirely.&lt;/strong&gt; &lt;code&gt;set_fees&lt;/code&gt; and &lt;code&gt;is_locked&lt;/code&gt; handle routine and urgent needs, which frees the upgrade path to be genuinely, usefully slow.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  ⚠️ Worth Understanding Correctly
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Only the Router is upgradeable" isn't a security weakness — it's a narrowed trust surface.&lt;/strong&gt; One contract with a seven-day public timelock is a far smaller thing to monitor than every pool being independently mutable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;is_locked&lt;/code&gt; is real, immediate admin power.&lt;/strong&gt; No timelock applies. That's appropriate for a circuit breaker, but it's an honest part of the trust model and shouldn't be glossed over.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New pool code means new pool addresses, not upgraded pools.&lt;/strong&gt; Liquidity doesn't migrate itself; LPs move deliberately, or stay exactly where they are under unchanged rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🏁 Bottom Line
&lt;/h2&gt;

&lt;p&gt;Router and Pool contracts have different upgrade models because they hold fundamentally different things. The Router holds routing logic and custody of Jetton wallets but no pair state, making it both worth upgrading and safe to upgrade — behind a seven-day timelock with publicly visible pending state. Pools hold nothing &lt;em&gt;but&lt;/em&gt; pair state, so they're deployed from a Router-held code template and left immutable, with new designs arriving as new addresses rather than rewrites of existing liquidity. On an async chain where the Router is a mandatory message hop rather than an optional convenience, that split isn't a compromise. It's the shape the architecture was always going to take.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔗 Sources &amp;amp; Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;STON.fi — Architecture Overview (router as sole upgradeable contract) — &lt;a href="https://docs.ston.fi/docs/developer-section/architecture" rel="noopener noreferrer"&gt;https://docs.ston.fi/docs/developer-section/architecture&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;STON.fi — Router (v2) Contract Reference (upgrade delays, governance messages) — &lt;a href="https://docs.ston.fi/developer-section/dex/smart-contracts/v2/router" rel="noopener noreferrer"&gt;https://docs.ston.fi/developer-section/dex/smart-contracts/v2/router&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;STON.fi — v2 Op Codes Reference — &lt;a href="https://docs.ston.fi/developer-section/dex/smart-contracts/v2/op-codes" rel="noopener noreferrer"&gt;https://docs.ston.fi/developer-section/dex/smart-contracts/v2/op-codes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;STON.fi — SDK v1 Reference (&lt;code&gt;getRouterData&lt;/code&gt;, &lt;code&gt;tempUpgrade&lt;/code&gt; structure) — &lt;a href="https://docs.ston.fi/developer-section/dex/sdk/v1/reference" rel="noopener noreferrer"&gt;https://docs.ston.fi/developer-section/dex/sdk/v1/reference&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;STON.fi — SDK Migration Guide v1 to v2 (CPI pools, explicit pool types) — &lt;a href="https://docs.ston.fi/developer-section/dex/sdk/migration/v1-to-v2" rel="noopener noreferrer"&gt;https://docs.ston.fi/developer-section/dex/sdk/migration/v1-to-v2&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;This article reflects independent research based on STON.fi's public developer documentation as of mid-2026. Contract behavior, upgrade delays, and op codes evolve as the protocol ships updates — always verify current details directly on docs.ston.fi before shipping a production integration.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devops</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>HTLC Timelocks in Cross-Chain Swap Design: A STONfi/Omniston Case Study</title>
      <dc:creator>Web3KD</dc:creator>
      <pubDate>Sun, 20 Sep 2026 10:23:35 +0000</pubDate>
      <link>https://dev.to/web3kd/htlc-timelocks-in-cross-chain-swap-design-a-stonfiomniston-case-study-3onl</link>
      <guid>https://dev.to/web3kd/htlc-timelocks-in-cross-chain-swap-design-a-stonfiomniston-case-study-3onl</guid>
      <description>&lt;h1&gt;
  
  
  ⏳ HTLC Timelocks in Cross-Chain Swap Design: A STON.fi/Omniston Case Study
&lt;/h1&gt;

&lt;p&gt;Most explanations of atomic swaps stop at "funds are locked behind a hash and a timer, so either both sides complete or both refund." That sentence is true and almost entirely useless for understanding why the design works, what it costs, or how a production system builds a real product on top of it.&lt;/p&gt;

&lt;p&gt;The interesting part isn't the hashlock. Hashlocks are trivial — a one-way function, a preimage, a comparison. The interesting part is the &lt;strong&gt;timelock&lt;/strong&gt;, and specifically the relationship between two timelocks on two chains that don't know each other exists. Get that relationship backwards and the atomicity guarantee inverts into an exploitable free option. Get it right and you have settlement that needs no bridge, no custodian, and no governance intervention when things go wrong.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"The hashlock is the part everyone explains. The timelock ordering is the part that actually keeps you from losing money."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🔐 Section 1: The Primitive — Two Doors, Never Both Open
&lt;/h2&gt;

&lt;p&gt;An HTLC holds funds behind exactly two release conditions, and the entire discipline of the design is that these two paths can never be open simultaneously.&lt;/p&gt;

&lt;p&gt;A minimal shape, expressed as the state a contract actually stores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;HtlcState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;hashlock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// sha256(secret) — public from the start&lt;/span&gt;
  &lt;span class="nl"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;       &lt;span class="c1"&gt;// unix timestamp, after which refund opens&lt;/span&gt;
  &lt;span class="nl"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// gets funds back via refund()&lt;/span&gt;
  &lt;span class="nl"&gt;receiver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// gets funds via claim(secret)&lt;/span&gt;
  &lt;span class="nl"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two methods, and the guard on each is what matters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;claim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;              &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;claim window closed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hashlock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wrong preimage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;receiver&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;refund&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;too early to refund&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;caller&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;not the sender&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at the two &lt;code&gt;now()&lt;/code&gt; checks. &lt;code&gt;claim&lt;/code&gt; requires &lt;em&gt;before&lt;/em&gt; the deadline; &lt;code&gt;refund&lt;/code&gt; requires &lt;em&gt;at or after&lt;/em&gt; it. They are exact complements — there is no timestamp at which both succeed, and no timestamp at which both fail. Before the deadline, exactly one party can act (the receiver, if they have the preimage). After it, exactly one party can act (the sender). There is no third state and no gap.&lt;/p&gt;

&lt;p&gt;The academic framing describes this as a tuple of three algorithms — &lt;strong&gt;Lock&lt;/strong&gt;, &lt;strong&gt;Unlock&lt;/strong&gt;, &lt;strong&gt;Refund&lt;/strong&gt; — and that framing is worth internalizing, because it makes explicit that "failure" here is a &lt;em&gt;first-class designed outcome&lt;/em&gt;, not an exception path bolted on afterward. A refund isn't the protocol breaking. It's the protocol working.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why the hash function choice actually matters.&lt;/strong&gt; The preimage has to be unguessable and the hash has to be identically computable on both chains. That second requirement is more constraining than it sounds — it rules out any pairing of chains that don't share a hash primitive, and it's why SHA-256 dominates in practice rather than anything more exotic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The secret must be generated with real entropy, not derived&lt;/span&gt;
&lt;span class="c1"&gt;// from anything predictable — a timestamp, a nonce, a counter.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hashlock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// hashlock is published immediately and publicly.&lt;/span&gt;
&lt;span class="c1"&gt;// secret stays private until the moment of claim.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A secret derived from anything guessable — a block hash, a sequence number, a timestamp — hands the counterparty the ability to claim without waiting. The randomness isn't a detail; it's load-bearing.&lt;/p&gt;

&lt;p&gt;There's also a subtlety in &lt;em&gt;where the secret becomes public&lt;/em&gt;. It isn't revealed by any out-of-band message or any trusted relay. It becomes public as a side effect of being used — the claim transaction carries it, and once that transaction is in a block, the preimage is simply readable chain state that anyone can observe. That's what makes the second leg trustless: nobody has to &lt;em&gt;send&lt;/em&gt; the counterparty anything.&lt;/p&gt;

&lt;p&gt;Here's where one HTLC stops being enough: this construction gives you a &lt;strong&gt;conditional payment&lt;/strong&gt;, not a swap. Alice can pay Bob conditionally. Nothing above causes Bob to pay Alice. A cross-chain swap needs &lt;em&gt;two&lt;/em&gt; of these, one per chain, and coordinating their deadlines is the genuinely hard problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚖️ Section 2: The Asymmetry That Makes It Atomic
&lt;/h2&gt;

&lt;p&gt;Two HTLCs, two chains, same hashlock. Alice locks on the source chain, Bob locks on the destination chain. Alice generated the secret, so initially only Alice can unlock anything.&lt;/p&gt;

&lt;p&gt;The question that decides whether this is safe or catastrophically broken:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;             ┌─────────────────────────────────────────┐
  SOURCE     │  Alice's lock          deadline: ???     │
             └─────────────────────────────────────────┘
             ┌─────────────────────────────────────────┐
  DEST       │  Bob's lock            deadline: ???     │
             └─────────────────────────────────────────┘

             Which deadline comes first?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The answer: they must be &lt;strong&gt;staggered, not synchronized&lt;/strong&gt;, and the direction is not arbitrary. &lt;em&gt;The party holding the secret must have the later deadline.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The one invariant the entire protocol rests on:&lt;/span&gt;
&lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;takerDeadline&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;originatorDeadline&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this direction, specifically?&lt;/strong&gt; Run the broken ordering and watch it fail.&lt;/p&gt;

&lt;p&gt;Suppose Bob's deadline came &lt;em&gt;after&lt;/em&gt; Alice's. Alice, holding the secret, simply does nothing. Her own lock expires first and she refunds — she has her original funds back, free and clear. But Bob's lock is still live, still behind the same hash, and Alice still knows the preimage. She reveals it, claims Bob's asset, and leaves holding both sides. The hashlock performed flawlessly. The deadline ordering destroyed the trade.&lt;/p&gt;

&lt;p&gt;Now the correct ordering. Alice wants the destination asset, so she must reveal the secret on the destination chain — and she must do it before the &lt;em&gt;earlier&lt;/em&gt; deadline. The moment she does, the preimage is public chain state. Bob reads it and claims on the source chain, and he is guaranteed a window to do so, because the source deadline is strictly later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  t₀ ─────────────────────────────────────────────────────▶ time

  DEST    [ Bob's lock ────────────────── ✕ takerDeadline ]
                          ▲
                          │ Alice reveals secret here
                          │ (must be before ✕)
                          ▼
  SOURCE  [ Alice's lock ─────────────────────── ✕ originatorDeadline ]
                          └──── Bob's safety window ────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gap — the span between the two deadlines — &lt;em&gt;is&lt;/em&gt; Bob's protection. It isn't slack or padding. It's the entire reason he can safely go second.&lt;/p&gt;

&lt;p&gt;The formal literature states this tightly: HTLCs realize cross-chain atomic swaps by coordinating two contracts on different chains with &lt;strong&gt;synchronized hashlocks and staggered timelocks&lt;/strong&gt;, and atomicity holds in the sense that either both assets transfer or both parties refund. Synchronized hash, staggered time. Those four words are the protocol.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enumerate every branch and it holds:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;th&gt;Source leg&lt;/th&gt;
&lt;th&gt;Destination leg&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Alice reveals and claims&lt;/td&gt;
&lt;td&gt;Bob claims with revealed secret&lt;/td&gt;
&lt;td&gt;Alice claims&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alice never reveals&lt;/td&gt;
&lt;td&gt;Refunds at later deadline&lt;/td&gt;
&lt;td&gt;Refunds at earlier deadline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alice reveals at the last instant&lt;/td&gt;
&lt;td&gt;Bob still has the full gap to act&lt;/td&gt;
&lt;td&gt;Alice claims&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bob never locks at all&lt;/td&gt;
&lt;td&gt;Alice's lock expires, refunds&lt;/td&gt;
&lt;td&gt;Never existed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There is &lt;strong&gt;no ordering of events&lt;/strong&gt; in which one side claims while the other side fails to either claim or refund. And note what's absent from that table: any row requiring a human to intervene. No multisig committee, no admin key, no bridge operator adjudicating whose claim is legitimate. Recovery is encoded in the contract and fires on a timer whether or not anyone is watching.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sizing that gap is its own engineering decision.&lt;/strong&gt; It can't be arbitrarily tight, because the second party needs real wall-clock time to observe the revealed secret, build a transaction, broadcast it, and get it confirmed — under whatever congestion exists at that moment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;takerDeadline&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;destinationChainFinality&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;SAFETY_FACTOR&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;originatorDeadline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;takerDeadline&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;reactionWindow&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// reactionWindow must exceed:&lt;/span&gt;
&lt;span class="c1"&gt;//   worst-case source-chain confirmation time&lt;/span&gt;
&lt;span class="c1"&gt;// + realistic reorg depth&lt;/span&gt;
&lt;span class="c1"&gt;// + the counterparty's own detection latency&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Too tight, and a congestion spike or reorg genuinely costs someone the trade. Too wide, and capital sits locked far longer than necessary in the failure case. Every production HTLC system is making a claim about the worst-case latency of the slower chain and encoding that claim as a number.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔄 Section 3: What Omniston Adds — Discovery Before Settlement
&lt;/h2&gt;

&lt;p&gt;The textbook atomic swap has a problem that makes it useless as a consumer product: it assumes Alice and Bob have already found each other and already agreed a price. In reality, finding a counterparty willing to take the other side of a cross-chain trade — competitively, right now, for your size — is the hard part. &lt;strong&gt;HTLC solves settlement. It does nothing about discovery.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Omniston's architecture puts a competitive quoting layer &lt;em&gt;in front of&lt;/em&gt; settlement, and the ordering is the whole point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  PHASE 1 — off-chain, free, reversible
  ────────────────────────────────────────────────────────
  user intent
      └─▶ RFQ broadcast ──▶ resolver A  ─┐
                        ──▶ resolver B  ─┼─▶ best quote wins
                        ──▶ resolver C  ─┘
                        ──▶ AMM pool reads

      ⚠️  NO HTLC EXISTS YET. Nothing has touched any chain.

  PHASE 2 — on-chain, committed, timelocked
  ────────────────────────────────────────────────────────
  winning quote
      └─▶ user locks source side    (later deadline)
      └─▶ resolver locks dest side  (earlier deadline)
      └─▶ reveal ──▶ claim ──▶ claim
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Phase one costs nothing.&lt;/strong&gt; Resolvers — independent market makers running their own pricing services over persistent gRPC streams — each decide independently what to offer for that size and pair. Quotes come back, get compared against AMM pool liquidity, best terms win.&lt;/p&gt;

&lt;p&gt;If no acceptable quote arrives, the RFQ simply expires. No HTLC was created. There is nothing to refund and nothing to unwind. This is exactly the right place to put the "no counterparty found" failure: free, off-chain, and instant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase two instantiates the two-contract construction for real&lt;/strong&gt; — but only after a specific resolver has won at a specific price.&lt;/p&gt;

&lt;p&gt;The boundary between those two phases deserves attention, because it's where a surprising amount of practical safety lives. A quote isn't a standing offer — it carries its own validity deadline, entirely separate from and much shorter than the HTLC timelocks that follow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Three distinct clocks, easy to conflate, doing different jobs:&lt;/span&gt;
&lt;span class="nx"&gt;quoteValidUntil&lt;/span&gt;       &lt;span class="c1"&gt;// seconds — how long this price is honored&lt;/span&gt;
&lt;span class="nx"&gt;takerDeadline&lt;/span&gt;         &lt;span class="c1"&gt;// minutes — destination-side HTLC expiry&lt;/span&gt;
&lt;span class="nx"&gt;originatorDeadline&lt;/span&gt;    &lt;span class="c1"&gt;// minutes — source-side HTLC expiry, strictly later&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Conflating those is a genuine source of confusion. A user who hesitates past &lt;code&gt;quoteValidUntil&lt;/code&gt; hasn't lost anything — no HTLC existed yet, and re-requesting simply produces a fresh price. A user who hesitates &lt;em&gt;after&lt;/em&gt; locking is in a different regime entirely, where the relevant clocks are the timelocks and the outcome is a refund rather than a re-quote.&lt;/p&gt;

&lt;p&gt;The structural detail worth highlighting: &lt;strong&gt;who funds the destination side.&lt;/strong&gt; Not a bridge contract holding pooled user deposits. Not the protocol treasury. The &lt;em&gt;resolver&lt;/em&gt; — a market maker who just won a competitive auction and is now putting its own capital behind the quote it gave.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Bridge model — concentrated, permanent, growing target&lt;/span&gt;
&lt;span class="nx"&gt;bridgeContract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lockedValue&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;everyUserDeposit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Resolver model — per-trade, per-counterparty, time-bounded&lt;/span&gt;
&lt;span class="nx"&gt;resolverCapital&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;thisTradeOnly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;releasesAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;takerDeadline&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a materially different risk shape. Exposure is bounded per trade and expires on a timer, instead of accumulating in one contract that becomes a larger target every day.&lt;/p&gt;

&lt;p&gt;This also explains something users routinely misread: &lt;strong&gt;why cross-chain swaps take noticeably longer than same-chain ones on STON.fi.&lt;/strong&gt; A same-chain TON swap inherits atomicity from TON's own transaction model — complete or revert, settled as fast as the chain includes it. A cross-chain swap can inherit nothing, because two independent blockchains offer each other no guarantees whatsoever. The extra time isn't latency awaiting better engineering. It &lt;strong&gt;is&lt;/strong&gt; the timelock window — a correctness guarantee, purchased in seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TON's async execution adds a wrinkle the classic papers don't contemplate.&lt;/strong&gt; The canonical atomic swap literature assumes synchronous execution: call a contract, it succeeds or reverts, you know immediately. TON passes messages instead — a message sent in one block resolves in a later one. A STON.fi swap already traverses a real message chain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user's jetton wallet
  └─▶ Router's jetton wallet   (transfer_notification)
      └─▶ Router               (decode payload, dispatch)
          └─▶ Pool             (execute AMM math)
              └─▶ settlement
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Layering HTLC settlement on top of that means "did the claim succeed?" is not answerable synchronously — which is precisely why trade tracking is a streaming subscription rather than a return value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;omniston&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trackTrade&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;rfqId&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 'filled' | 'partiallyFilled' | 'aborted'&lt;/span&gt;
  &lt;span class="c1"&gt;// async settlement means you observe, you don't await&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  💸 Section 4: What Atomicity Actually Costs
&lt;/h2&gt;

&lt;p&gt;An engineering article that only lists advantages isn't an engineering article. This guarantee is paid for in four distinct currencies.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;⏱️ &lt;strong&gt;Latency — paid by the user.&lt;/strong&gt; The timelock window can't compress below the slower chain's worst-case confirmation time plus margin. The trade-off is explicit in the literature: HTLCs trade a little latency — you pay for the timelock window — for the guarantee that at no point does any party hold the other's asset without an enforceable reason to release it. Seconds for certainty is usually a good trade. It's still a trade.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🔒 &lt;strong&gt;Capital lockup — paid by the resolver.&lt;/strong&gt; From funding its leg until settlement, that capital is committed and unusable. If the swap refunds, the resolver recovers principal but earned nothing for the time it was immobilized. Pure opportunity cost — and a real component of why cross-chain quotes are structurally wider than same-chain AMM pricing. You are partly paying for the capital your trade froze.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;⛽ &lt;strong&gt;Gas on the failure path — paid by whoever refunds.&lt;/strong&gt; A refund is a transaction and costs gas. A swap that correctly returns everyone's funds is a &lt;em&gt;successful outcome&lt;/em&gt; by design, but both parties are still slightly out of pocket on a trade that produced nothing. Small, but worth naming because it's the cost users least expect.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🎣 &lt;strong&gt;Griefing surface — paid in expected value.&lt;/strong&gt; A counterparty who locks and then never reveals costs the other side nothing in principal — the refund handles it — but does cost them the window during which capital was frozen. Repeatedly initiating and abandoning swaps is a denial-of-service pattern against a resolver's working capital. The literature is candid that basic HTLCs aren't expressive enough for every multi-party scenario, and that advanced models exist specifically to improve incentive compatibility and resist bribery and collusion. Production systems generally mitigate this at the reputation layer — a resolver seeing repeated abandonment simply stops quoting that source — rather than cryptographically.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these invalidate the design. They define its &lt;strong&gt;domain of applicability&lt;/strong&gt;. For a high-frequency, low-value, same-chain swap this would be absurd overhead. For moving meaningful size across a chain boundary, where the alternative is trusting a custodian or a bridge holding concentrated value, it's a sound trade.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧭 Section 5: Walking Every Failure Mode
&lt;/h2&gt;

&lt;p&gt;Understanding a settlement design means answering "what happens if" for every branch. Here is each realistic failure and exactly what the construction does.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;📭 &lt;strong&gt;No resolver quotes the trade.&lt;/strong&gt; RFQ expires. Nothing touched any chain, no HTLC existed, nothing to refund. Adjust size or slippage and re-submit. The cheapest possible failure, correctly placed first.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🚪 &lt;strong&gt;User gets a quote but never confirms.&lt;/strong&gt; Same outcome — quotes carry their own validity deadline. Someone who walks away mid-flow loses nothing but time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🔇 &lt;strong&gt;Resolver locks, user abandons before revealing.&lt;/strong&gt; Source leg refunds at the later deadline, destination leg at the earlier one. Both recover principal; both are out refund gas; the resolver ate the opportunity cost of frozen capital. The trade simply didn't happen.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🐌 &lt;strong&gt;User reveals, destination chain congests severely.&lt;/strong&gt; This is the exact scenario the gap exists for. The secret is now public. The resolver has the entire window between deadlines to use it on the source side. Sized with a realistic worst case, the claim succeeds despite congestion. Sized optimistically, &lt;em&gt;this is where the design fails&lt;/em&gt; — which is why gap sizing is a serious parameter, not a config afterthought.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🔀 &lt;strong&gt;Chain reorganization reverses a claim.&lt;/strong&gt; Functionally similar: re-broadcast, re-confirm, and survivability depends on whether the gap exceeds realistic reorg depth. Every cross-chain design makes a finality assumption here; an HTLC system just makes it legible as a number instead of burying it in a validator set.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ &lt;strong&gt;Everything works.&lt;/strong&gt; User reveals on the destination chain and receives their asset. Resolver reads the secret from public chain state and claims on the source. Both legs settle. Elapsed time is roughly two chains' confirmation latency plus reaction time — measurably slower than same-chain, and correct &lt;em&gt;by construction&lt;/em&gt; rather than by trust.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The property worth restating: across every branch above, &lt;strong&gt;no outcome requires manual intervention.&lt;/strong&gt; No support ticket resolves a stuck HTLC, because there is no stuck state. The refund isn't a customer service process — it's a timer that fires whether or not anyone is paying attention.&lt;/p&gt;




&lt;h2&gt;
  
  
  🏁 Bottom Line
&lt;/h2&gt;

&lt;p&gt;HTLC-based cross-chain settlement is one of the rare designs where the security argument is genuinely &lt;em&gt;complete&lt;/em&gt; — you can enumerate every branch and verify each terminates fairly, without appealing to anyone's honesty or any committee's diligence. The hashlock provides conditional release. The &lt;strong&gt;staggered&lt;/strong&gt; timelocks, with the secret-holder's leg expiring strictly later, provide atomicity. Reverse that stagger and you've converted a safe swap into a free option for whoever holds the preimage.&lt;/p&gt;

&lt;p&gt;What Omniston contributes on top is the part HTLC alone can't: &lt;strong&gt;discovery&lt;/strong&gt;. Running a competitive RFQ auction among independent resolvers &lt;em&gt;before&lt;/em&gt; any funds touch any chain separates "can I get a good price" — which should be free and off-chain — from "will this settle safely," which is where the cryptography belongs. Resolvers funding the destination side with their own capital keeps failure exposure per-trade and time-bounded rather than concentrated and permanent.&lt;/p&gt;

&lt;p&gt;The costs are real: users pay in latency, resolvers in immobilized capital, both in gas on the failure path. In exchange there's no custodian, no bridge validator set, and no state where funds sit stranded awaiting a human decision. For anyone evaluating cross-chain designs seriously, that last property — &lt;strong&gt;a failure mode that resolves on a timer rather than through governance&lt;/strong&gt; — is the one worth weighing most heavily.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔗 Sources &amp;amp; Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Bitcoin Optech — HTLC topic overview (hashlock and timelock clauses): &lt;a href="https://bitcoinops.org/en/topics/htlc/" rel="noopener noreferrer"&gt;https://bitcoinops.org/en/topics/htlc/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;EmergentMind — Hash-Time-Lock Contracts (staggered timelock formalization): &lt;a href="https://www.emergentmind.com/topics/hash-time-lock-htl-contracts" rel="noopener noreferrer"&gt;https://www.emergentmind.com/topics/hash-time-lock-htl-contracts&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;DEV Community — "Atomic cross-chain trades, explained: a hands-on walkthrough of HTLCs": &lt;a href="https://dev.to/barissozen/atomic-cross-chain-trades-explained-a-hands-on-walkthrough-of-htlcs-4344"&gt;https://dev.to/barissozen/atomic-cross-chain-trades-explained-a-hands-on-walkthrough-of-htlcs-4344&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;DEV Community — "FAQ: What Happens If a Swap Fails or Stalls?" (RFQ-before-HTLC failure ordering): &lt;a href="https://dev.to/barissozen/faq-what-happens-if-a-hashlock-markets-swap-fails-or-stalls-3afm"&gt;https://dev.to/barissozen/faq-what-happens-if-a-hashlock-markets-swap-fails-or-stalls-3afm&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;arXiv — "xRWA: A Cross-Chain Framework for Interoperability of Real-World Assets" (Lock/Unlock/Refund formalization): &lt;a href="https://arxiv.org/pdf/2509.12957" rel="noopener noreferrer"&gt;https://arxiv.org/pdf/2509.12957&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;STON.fi Developer Docs — Omniston Protocol overview: &lt;a href="https://docs.ston.fi/developer-section/omniston" rel="noopener noreferrer"&gt;https://docs.ston.fi/developer-section/omniston&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;STON.fi Blog — "What Is Atomic Swap Execution and Why Does It Matter?": &lt;a href="https://blog.ston.fi/what-is-atomic-swap-execution-and-why-does-it-matter/" rel="noopener noreferrer"&gt;https://blog.ston.fi/what-is-atomic-swap-execution-and-why-does-it-matter/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This article reflects public cryptographic literature and STON.fi's developer documentation as of mid-2026. Code samples are illustrative pseudocode written to clarify the mechanism — they are not copied from any production contract. Protocol mechanics, supported chains, and timelock parameters evolve as the system ships updates; verify against docs.ston.fi before building or trading against any of it.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>cryptocurrency</category>
      <category>programming</category>
    </item>
    <item>
      <title>⚛ Add STON.fi Swaps to a React App with TON Connect</title>
      <dc:creator>Web3KD</dc:creator>
      <pubDate>Sat, 19 Sep 2026 13:25:30 +0000</pubDate>
      <link>https://dev.to/web3kd/add-stonfi-swaps-to-a-react-app-with-ton-connect-2mdc</link>
      <guid>https://dev.to/web3kd/add-stonfi-swaps-to-a-react-app-with-ton-connect-2mdc</guid>
      <description>&lt;h1&gt;
  
  
  ⚛️ Add STON.fi Swaps to a React App with TON Connect
&lt;/h1&gt;

&lt;p&gt;There's a specific moment in building a swap feature where the abstractions stop being theoretical: the user clicks a button, their wallet opens, and real tokens move. Everything before that is plumbing you can iterate on freely. Everything after it is irreversible.&lt;/p&gt;

&lt;p&gt;This walkthrough builds that entire path in React — wallet connection through TON Connect, live quotes through STON.fi's Omniston aggregation layer, transaction construction, signing, and status tracking — and pays particular attention to the places where the two libraries meet, because that's where most integrations actually break.&lt;/p&gt;

&lt;p&gt;The division of labor is worth stating up front, since confusing it causes most of the early confusion: &lt;strong&gt;Omniston knows prices and builds transactions. TON Connect signs them.&lt;/strong&gt; Omniston never touches a private key; TON Connect never knows what a swap is. They're deliberately decoupled, and the code you write is mostly the seam between them.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"The SDK builds the message. The wallet decides whether it happens. Keep those two responsibilities separate in your head and the architecture makes sense."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🧰 What You'll Need
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Node.js 18+&lt;/strong&gt; and a React project (Vite or Next.js both work; examples here are framework-agnostic React)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A TON wallet&lt;/strong&gt; for testing — Tonkeeper or MyTonWallet, both TON Connect–compatible&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A publicly reachable URL&lt;/strong&gt; to host your TON Connect manifest (more on this below — it's the first thing that trips people up)&lt;/li&gt;
&lt;li&gt;Basic React: hooks, component state, &lt;code&gt;async/await&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Install the two packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @ston-fi/omniston-sdk-react
npm &lt;span class="nb"&gt;install&lt;/span&gt; @tonconnect/ui-react
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;@ston-fi/omniston-sdk-react&lt;/code&gt; is built on RxJS observables and TanStack Query under the hood, which means loading states, retries, and subscription cleanup come handled rather than hand-rolled. &lt;code&gt;@tonconnect/ui-react&lt;/code&gt; gives you a drop-in connect button and the signing interface.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔐 Section 1: Wallet Connection with TON Connect
&lt;/h2&gt;

&lt;p&gt;Nothing else works until a wallet is connected, so start here.&lt;/p&gt;

&lt;p&gt;TON Connect requires a &lt;strong&gt;manifest&lt;/strong&gt; — a small JSON file describing your app to the wallet that's about to trust it. Create &lt;code&gt;public/tonconnect-manifest.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://your-app-domain.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"My Swap App"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iconUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://your-app-domain.com/icon-180x180.png"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three practical notes that save real debugging time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🌐 &lt;strong&gt;The manifest must be reachable over public HTTPS.&lt;/strong&gt; Wallet apps fetch it from their own servers or devices — they cannot reach your &lt;code&gt;localhost&lt;/code&gt;. For local development, tunnel your dev server (ngrok, Cloudflare Tunnel) and point &lt;code&gt;manifestUrl&lt;/code&gt; at the tunnel.&lt;/li&gt;
&lt;li&gt;🖼️ &lt;strong&gt;The icon URL must actually resolve.&lt;/strong&gt; A 404 here produces a connection failure that gives you no useful error message at all.&lt;/li&gt;
&lt;li&gt;🔗 &lt;strong&gt;The &lt;code&gt;url&lt;/code&gt; field should match the domain serving your app.&lt;/strong&gt; Mismatches get flagged by some wallets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now wire up the provider. Create &lt;code&gt;src/providers/AppProviders.tsx&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;TonConnectUIProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@tonconnect/ui-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;manifestUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://your-app-domain.com/tonconnect-manifest.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;AppProviders&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PropsWithChildren&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TonConnectUIProvider&lt;/span&gt; &lt;span class="na"&gt;manifestUrl&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;manifestUrl&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;TonConnectUIProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that in place, a connect button is one line anywhere in your tree:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;TonConnectButton&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@tonconnect/ui-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;header&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex justify-end p-4"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TonConnectButton&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;header&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;TonConnectButton&lt;/code&gt; handles the wallet-selection modal, deep-linking into mobile wallet apps, session persistence across reloads, and the connected/disconnected states. You don't build any of that.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔌 Section 2: The Omniston Provider
&lt;/h2&gt;

&lt;p&gt;Next, give your component tree access to Omniston. Extend &lt;code&gt;AppProviders.tsx&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;TonConnectUIProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@tonconnect/ui-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Omniston&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;OmnistonProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@ston-fi/omniston-sdk-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;manifestUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://your-app-domain.com/tonconnect-manifest.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;omniston&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Omniston&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_OMNISTON_URL&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wss://omni-ws-sandbox.ston.fi&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;AppProviders&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PropsWithChildren&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TonConnectUIProvider&lt;/span&gt; &lt;span class="na"&gt;manifestUrl&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;manifestUrl&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OmnistonProvider&lt;/span&gt; &lt;span class="na"&gt;omniston&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;omniston&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;OmnistonProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;TonConnectUIProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two deliberate decisions here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code&gt;Omniston&lt;/code&gt; instance is created outside the component.&lt;/strong&gt; If you construct it inside the component body, every re-render creates a new client and tears down the previous WebSocket connection. Your quote stream will reconnect constantly and you'll spend an afternoon wondering why quotes keep vanishing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The endpoint defaults to sandbox, not production.&lt;/strong&gt; Omniston exposes &lt;code&gt;wss://omni-ws-sandbox.ston.fi&lt;/code&gt;, which mirrors the production API surface exactly, letting you develop against real shapes without touching real liquidity. Defaulting to sandbox means a misconfigured build fails toward "nothing real happens" rather than "real swaps executed against production." Switch deliberately, via environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;VITE_OMNISTON_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;wss://omni-ws.ston.fi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your app already uses TanStack Query, pass your existing client so Omniston reuses it rather than spinning up a second:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;QueryClientProvider&lt;/span&gt; &lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OmnistonProvider&lt;/span&gt; &lt;span class="na"&gt;omniston&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;omniston&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;OmnistonProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;QueryClientProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  💬 Section 3: Requesting Live Quotes
&lt;/h2&gt;

&lt;p&gt;Here's the concept that most distinguishes Omniston from a typical price API, and the one most worth internalizing before writing UI around it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;useRfq()&lt;/code&gt; is a subscription, not a fetch.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It doesn't resolve once with a price. It opens a stream that stays alive and emits updated quotes as market conditions change. Your component will re-render multiple times with new quotes for a single user input, and that's correct behavior, not a bug.&lt;/p&gt;

&lt;p&gt;Start with settlement parameters — these tell Omniston which execution paths you'll accept. Create &lt;code&gt;src/swap/settlement.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;SettlementParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;SwapSettlementParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;OrderSettlementParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@ston-fi/omniston-sdk-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;swapAndOrderSettlementParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SettlementParams&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;$case&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;swap&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;maxPriceSlippagePips&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 1%&lt;/span&gt;
        &lt;span class="na"&gt;flexibleIntegratorFee&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;satisfies&lt;/span&gt; &lt;span class="nx"&gt;SwapSettlementParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;$case&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;order&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="nx"&gt;satisfies&lt;/span&gt; &lt;span class="nx"&gt;OrderSettlementParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️ &lt;strong&gt;&lt;code&gt;maxPriceSlippagePips&lt;/code&gt; is measured in pips — hundredths of a basis point.&lt;/strong&gt; &lt;code&gt;10_000&lt;/code&gt; is 1%. Not 100%. Not 1 bp. This is genuinely easy to get wrong by an order of magnitude, and a too-loose value doesn't throw an error — it just silently removes the protection slippage exists for. Annotate it with the percentage every time you write it.&lt;/p&gt;

&lt;p&gt;Including both &lt;code&gt;swap&lt;/code&gt; and &lt;code&gt;order&lt;/code&gt; settlement types is the sensible default: &lt;code&gt;swap&lt;/code&gt; routes through on-chain AMM pools, &lt;code&gt;order&lt;/code&gt; routes through resolvers using signed orders or HTLC escrow. On thin pairs, &lt;code&gt;order&lt;/code&gt; settlement is often the only path that returns a quote at all. Restricting to swap-only means seeing &lt;code&gt;noQuote&lt;/code&gt; in situations where a perfectly good quote existed.&lt;/p&gt;

&lt;p&gt;Now the assets and the hook. Create &lt;code&gt;src/swap/useSwapQuote.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;useRfq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AssetId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;QuoteRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@ston-fi/omniston-sdk-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;swapAndOrderSettlementParams&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./settlement&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;tonJetton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;AssetId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;$case&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ton&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$case&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jetton&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useSwapQuote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;inputAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;outputAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;inputBaseUnits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;quoteRequest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;QuoteRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;inputAsset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;tonJetton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inputAddress&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;outputAsset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;tonJetton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputAddress&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;$case&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;inputUnits&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inputBaseUnits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;settlementParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;swapAndOrderSettlementParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;useRfq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;quoteRequest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the component consuming it, handling &lt;strong&gt;every&lt;/strong&gt; event the stream can emit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useSwapQuote&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./useSwapQuote&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;QuotePanel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;inputAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;outputAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;inputBaseUnits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;inputAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;outputAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;inputBaseUnits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSwapQuote&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;inputAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;outputAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;inputBaseUnits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inputBaseUnits&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-red-500"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Couldn't reach the quote service.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;$case&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ack&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Searching for the best route…&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;quoteUpdated&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;QuoteCard&lt;/span&gt; &lt;span class="na"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;noQuote&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;No route available for this pair right now.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unsubscribed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Quote stream closed — refresh to try again.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Waiting for a quote…&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Handling only &lt;code&gt;quoteUpdated&lt;/code&gt; — which is what most first attempts do — leaves your UI blank in three situations where it should be telling the user something specific:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📡 &lt;strong&gt;&lt;code&gt;ack&lt;/code&gt;&lt;/strong&gt; — Omniston received the request and assigned an &lt;code&gt;rfqId&lt;/code&gt;. Good for a loading state; essential for debugging a stream that then produces nothing.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;&lt;code&gt;quoteUpdated&lt;/code&gt;&lt;/strong&gt; — a quote arrived. &lt;strong&gt;Fires repeatedly.&lt;/strong&gt; Not a completion signal.&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;&lt;code&gt;noQuote&lt;/code&gt;&lt;/strong&gt; — no connected source could fill this. Normal on illiquid pairs or oversized requests. Information, not an error.&lt;/li&gt;
&lt;li&gt;🔌 &lt;strong&gt;&lt;code&gt;unsubscribed&lt;/code&gt;&lt;/strong&gt; — the server ended the stream. Anything displayed is now stale.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🏗️ Section 4: Building the Swap Transaction
&lt;/h2&gt;

&lt;p&gt;Once a user has a quote in front of them and wants to proceed, you build the actual transaction. Branch on the settlement type first — a quote can come back as either kind, and calling the wrong builder fails.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;useTonBuildSwap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ChainAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;QuoteOfType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@ston-fi/omniston-sdk-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useBuildSwapTx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;QuoteOfType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;swap&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;traderAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ChainAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;useTonBuildSwap&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;quoteId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quoteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;transferSrcAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;traderAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;refundSrcAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;traderAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;gasExcessAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;traderAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;traderDstAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;traderAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those four address parameters look redundant — in the common case they're all the same connected wallet — but each has a distinct role, and understanding them matters when something goes wrong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📤 &lt;strong&gt;&lt;code&gt;transferSrcAddress&lt;/code&gt;&lt;/strong&gt; — where input tokens are pulled from&lt;/li&gt;
&lt;li&gt;↩️ &lt;strong&gt;&lt;code&gt;refundSrcAddress&lt;/code&gt;&lt;/strong&gt; — where funds return if the swap fails partway&lt;/li&gt;
&lt;li&gt;⛽ &lt;strong&gt;&lt;code&gt;gasExcessAddress&lt;/code&gt;&lt;/strong&gt; — where unused gas is returned&lt;/li&gt;
&lt;li&gt;📥 &lt;strong&gt;&lt;code&gt;traderDstAddress&lt;/code&gt;&lt;/strong&gt; — where output tokens land&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keeping them as separate parameters means the same API supports more advanced flows later (swapping on behalf of a smart-contract wallet, sending output to a different address than the input came from) without a breaking change.&lt;/p&gt;

&lt;p&gt;For branching, the SDK ships type guards — &lt;code&gt;isSwapQuote&lt;/code&gt;, &lt;code&gt;isOrderQuote&lt;/code&gt;, &lt;code&gt;isHtlcOrderQuote&lt;/code&gt;, and &lt;code&gt;matchQuoteByType&lt;/code&gt;. Use them rather than reaching into &lt;code&gt;quote.settlementData?.$case&lt;/code&gt; manually; they narrow the TypeScript type correctly, so your editor tells you which fields actually exist on each variant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isSwapQuote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isOrderQuote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Quote&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@ston-fi/omniston-sdk-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SettlementRouter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Quote&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isSwapQuote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SwapFlow&lt;/span&gt; &lt;span class="na"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isOrderQuote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderFlow&lt;/span&gt; &lt;span class="na"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Unsupported settlement type.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ✍️ Section 5: Signing and Sending with TON Connect
&lt;/h2&gt;

&lt;p&gt;This is the seam. &lt;code&gt;useTonBuildSwap()&lt;/code&gt; returns &lt;strong&gt;unsigned messages&lt;/strong&gt; — it does not sign or broadcast anything. That's intentional: signing is wallet territory, and Omniston stays wallet-agnostic. Your job is to hand those messages to TON Connect.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useTonConnectUI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useTonAddress&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@tonconnect/ui-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useBuildSwapTx&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./useBuildSwapTx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;QuoteOfType&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@ston-fi/omniston-sdk-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SwapButton&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;QuoteOfType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;swap&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tonConnectUI&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTonConnectUI&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rawAddress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTonAddress&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;traderAddress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$case&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ton&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rawAddress&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;swapTx&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useBuildSwapTx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;traderAddress&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleSwap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;swapTx&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tonConnectUI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendTransaction&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;validUntil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 5 minutes&lt;/span&gt;
        &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;swapTx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})),&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="c1"&gt;// result.boc contains the signed message — needed for tracking&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Submitted:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;boc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isUserRejection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Not an error — the user changed their mind&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Swap failed:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleSwap&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;swapTx&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;rawAddress&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;rawAddress&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Confirm Swap&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Connect wallet first&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things in there deserve more than a passing glance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⏱️ &lt;code&gt;validUntil&lt;/code&gt; matters more than it looks.&lt;/strong&gt; Set it too far in the future and a stale transaction can sit in the wallet's queue, then execute against a quote that's no longer accurate — potentially at a materially worse price than the user agreed to. Five minutes is a reasonable default: long enough that a slow mobile wallet doesn't time out, short enough that market movement doesn't invalidate the trade.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🙅 User rejection is not an error.&lt;/strong&gt; People open the confirmation dialog and close it. That's normal behavior. If rejections land in the same error bucket as genuine on-chain failures, your monitoring will show a "problem" that's really just users changing their minds — and you'll chase phantom bugs. Handle it as a distinct, quiet path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📦 Keep the &lt;code&gt;boc&lt;/code&gt;.&lt;/strong&gt; The response contains the signed message as a bag of cells. You need an identifier derived from it for the tracking step, so don't discard it.&lt;/p&gt;




&lt;h2&gt;
  
  
  📍 Section 6: Tracking the Swap to Completion
&lt;/h2&gt;

&lt;p&gt;Sending isn't the end of the user's experience. They want to know whether it actually worked. &lt;code&gt;swapTrack()&lt;/code&gt; gives you a live status stream.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;useOmniston&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ChainAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Quote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@ston-fi/omniston-sdk-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useSwapTracking&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;traderAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;outgoingTxQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Quote&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;traderAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ChainAddress&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;outgoingTxQuery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;omniston&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useOmniston&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setStatus&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Submitting…&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;unsubscribe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nx"&gt;omniston&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;swapTrack&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;quoteId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quoteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;traderAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;outgoingTxQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;$case&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;awaitingTransfer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;setStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Waiting for your transfer to confirm…&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
              &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;progress&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;setStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Swap status: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
              &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unsubscribed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;setStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Tracking ended&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="nx"&gt;unsubscribe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;omniston&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quoteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;traderAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;outgoingTxQuery&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cleanup function returned from &lt;code&gt;useEffect&lt;/code&gt; is not optional here. Without it, every re-render with changed dependencies leaves an orphaned subscription running, and in a component users interact with repeatedly, those accumulate until something noticeably degrades.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;outgoingTxQuery&lt;/code&gt; can be a transaction hash, a message hash, or the outgoing message body — whichever identifier your setup surfaces most reliably. You derive it from the &lt;code&gt;boc&lt;/code&gt; returned by &lt;code&gt;sendTransaction()&lt;/code&gt;. The exact decode helper depends on your TON Connect version and which TON library you're using, so check the current TON Connect docs rather than assuming the API surface hasn't shifted.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚠️ Section 7: Mistakes That Cost Real Debugging Time
&lt;/h2&gt;

&lt;p&gt;Consolidated, in rough order of how often they bite people:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔁 &lt;strong&gt;Creating &lt;code&gt;new Omniston()&lt;/code&gt; inside a component.&lt;/strong&gt; Constant reconnection, vanishing quotes. Instantiate at module scope.&lt;/li&gt;
&lt;li&gt;📉 &lt;strong&gt;Using a stale quote.&lt;/strong&gt; &lt;code&gt;useRfq()&lt;/code&gt; emits repeatedly. Always build the transaction from the &lt;strong&gt;latest&lt;/strong&gt; &lt;code&gt;quoteUpdated&lt;/code&gt;, not a cached one from when the user first looked.&lt;/li&gt;
&lt;li&gt;🔢 &lt;strong&gt;Hardcoding token decimals.&lt;/strong&gt; Many TON jettons use 9, but USDT uses 6. Read &lt;code&gt;decimals&lt;/code&gt; from asset metadata — hardcoding is a silent 1000× error.&lt;/li&gt;
&lt;li&gt;📏 &lt;strong&gt;Misreading pips.&lt;/strong&gt; &lt;code&gt;10_000&lt;/code&gt; = 1%.&lt;/li&gt;
&lt;li&gt;🎯 &lt;strong&gt;Assuming every quote is a swap quote.&lt;/strong&gt; Code that only calls &lt;code&gt;useTonBuildSwap()&lt;/code&gt; breaks the moment an order-settled quote arrives. Branch with the type guards.&lt;/li&gt;
&lt;li&gt;🚨 &lt;strong&gt;Logging user rejections as errors.&lt;/strong&gt; Pollutes monitoring and manufactures phantom bugs.&lt;/li&gt;
&lt;li&gt;🌐 &lt;strong&gt;A manifest on &lt;code&gt;localhost&lt;/code&gt;.&lt;/strong&gt; Wallets can't reach it. Tunnel for local dev.&lt;/li&gt;
&lt;li&gt;🧹 &lt;strong&gt;Skipping &lt;code&gt;useEffect&lt;/code&gt; cleanup&lt;/strong&gt; on tracking subscriptions.&lt;/li&gt;
&lt;li&gt;📌 &lt;strong&gt;Trusting a caret version range.&lt;/strong&gt; The SDK is pre-1.0, meaning breaking changes can land in minor releases under semver. Pin it:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@ston-fi/omniston-sdk-react"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.8.0"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🚀 Section 8: From Working to Production-Ready
&lt;/h2&gt;

&lt;p&gt;The flow above is complete but minimal. Before real users touch it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🪙 &lt;strong&gt;Replace hardcoded token addresses&lt;/strong&gt; with a proper picker backed by &lt;code&gt;@ston-fi/api&lt;/code&gt;, so users can swap any supported pair rather than the two you chose for testing.&lt;/li&gt;
&lt;li&gt;🎚️ &lt;strong&gt;Expose slippage as a user setting&lt;/strong&gt; rather than a constant. Traders on volatile pairs legitimately want to widen or tighten it themselves.&lt;/li&gt;
&lt;li&gt;🕳️ &lt;strong&gt;Design the &lt;code&gt;noQuote&lt;/code&gt; state properly.&lt;/strong&gt; A blank panel reads as "broken app." Explain that no route was found and suggest a smaller amount.&lt;/li&gt;
&lt;li&gt;🛡️ &lt;strong&gt;Add an error boundary around the signing step&lt;/strong&gt;, since wallet interactions fail in ways that shouldn't take down your whole interface.&lt;/li&gt;
&lt;li&gt;🧪 &lt;strong&gt;Test against sandbox first&lt;/strong&gt; — the endpoint exists precisely so your first end-to-end run isn't also your first real trade.&lt;/li&gt;
&lt;li&gt;🔍 &lt;strong&gt;Inspect the quote object yourself.&lt;/strong&gt; Field names carrying output amounts have shifted between SDK versions. Rather than trusting any written guide, including this one, log it once and read what you actually receive:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;quoteUpdated&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That takes thirty seconds and is strictly more reliable than secondhand documentation against a pre-1.0 package.&lt;/p&gt;




&lt;h2&gt;
  
  
  🏁 Wrapping Up
&lt;/h2&gt;

&lt;p&gt;What you've built is the complete loop every TON swap feature needs: wallet connection, a live quote subscription, settlement-aware transaction building, wallet signing, and status tracking to completion. The pieces Omniston handles for you — multi-source routing across STON.fi's pools, other TON DEXs, and RFQ resolvers; quote refreshing; settlement-type branching — are precisely the parts that are tedious and error-prone to write against raw contracts.&lt;/p&gt;

&lt;p&gt;The architectural insight worth carrying forward is the separation this whole integration is built around. Omniston produces unsigned messages and has no idea who you are. TON Connect signs messages and has no idea what a swap is. Your code is the seam between them, and keeping that boundary clean is what makes the rest — adding a token picker, supporting order settlement, handling cross-chain flows — incremental work rather than a rewrite.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔗 Sources &amp;amp; Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;STON.fi Developer Docs — Omniston React SDK (v1beta8): &lt;a href="https://docs.ston.fi/developer-section/omniston/sdk/react" rel="noopener noreferrer"&gt;https://docs.ston.fi/developer-section/omniston/sdk/react&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;STON.fi Developer Docs — Omniston Node.js SDK: &lt;a href="https://docs.ston.fi/developer-section/omniston/sdk/nodejs" rel="noopener noreferrer"&gt;https://docs.ston.fi/developer-section/omniston/sdk/nodejs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;STON.fi — Omniston Quickstart Guide (React): &lt;a href="https://docs.ston.fi/developer-section/quickstart/omniston" rel="noopener noreferrer"&gt;https://docs.ston.fi/developer-section/quickstart/omniston&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Omniston SDK — React v0.7 → v0.8 migration guide: &lt;a href="https://docs.ston.fi/developer-section/omniston/sdk/migration-v0.7-to-v0.8" rel="noopener noreferrer"&gt;https://docs.ston.fi/developer-section/omniston/sdk/migration-v0.7-to-v0.8&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Omniston SDK — GitHub source and example React app: &lt;a href="https://github.com/ston-fi/omniston-sdk/tree/main/examples/react-app" rel="noopener noreferrer"&gt;https://github.com/ston-fi/omniston-sdk/tree/main/examples/react-app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@ston-fi/omniston-sdk-react&lt;/code&gt; on npm: &lt;a href="https://www.npmjs.com/package/@ston-fi/omniston-sdk-react" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@ston-fi/omniston-sdk-react&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;TON Connect UI React documentation: &lt;a href="https://docs.tonconnect.io/docs/quick-start/react" rel="noopener noreferrer"&gt;https://docs.tonconnect.io/docs/quick-start/react&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This walkthrough reflects the STON.fi Omniston React SDK documentation as of mid-2026 (&lt;code&gt;v1beta8&lt;/code&gt; API surface). The SDK is pre-1.0 and under active development — hook signatures, event shapes, and quote field names can change between minor releases. Verify against the current docs at docs.ston.fi and the SDK CHANGELOG before shipping, and always run an end-to-end test against the sandbox endpoint before pointing a signing flow at production.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>💱 Build a STON.fi Swap Quote Viewer with TypeScript</title>
      <dc:creator>Web3KD</dc:creator>
      <pubDate>Sat, 19 Sep 2026 13:00:05 +0000</pubDate>
      <link>https://dev.to/web3kd/build-a-stonfi-swap-quote-viewer-with-typescript-1a50</link>
      <guid>https://dev.to/web3kd/build-a-stonfi-swap-quote-viewer-with-typescript-1a50</guid>
      <description>&lt;h1&gt;
  
  
  💱 Build a STON.fi Swap Quote Viewer with TypeScript
&lt;/h1&gt;

&lt;p&gt;Most swap tutorials rush you toward a signed transaction. This one deliberately stops short of it.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;quote viewer&lt;/strong&gt; — a tool that requests live pricing from STON.fi's Omniston aggregation layer and displays it, without ever touching a wallet — is genuinely the best first thing to build on this stack. It has no signing, no private keys, no irreversible actions, and no way to lose money while you're still learning the API surface. It's also the component you'll end up reusing in every larger project: a swap UI, a price monitor, an arbitrage scanner, a Telegram bot, a spreadsheet feed.&lt;/p&gt;

&lt;p&gt;By the end of this walkthrough you'll have a working TypeScript application that fetches real token metadata from STON.fi, opens a live quote stream through Omniston, handles every event the stream can emit, formats raw blockchain units into numbers a human can read, and prints a continuously-updating quote to your terminal.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Build the read-only version first. You can't accidentally spend anything while you're still learning the shape of the API."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🧰 What You'll Need
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Node.js 18+&lt;/strong&gt; — the SDK targets modern Node&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript&lt;/strong&gt; familiarity at a basic level (types, &lt;code&gt;async/await&lt;/code&gt;, interfaces)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No wallet, no funds, no testnet setup&lt;/strong&gt; — this entire project is read-only&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No prior TON or blockchain development experience is required. If you've called a REST API and subscribed to an event stream in JavaScript before, you already have the mental model you need.&lt;/p&gt;

&lt;p&gt;Two packages do the heavy lifting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📦 &lt;strong&gt;&lt;code&gt;@ston-fi/omniston-sdk&lt;/code&gt;&lt;/strong&gt; — the quote engine. Talks to Omniston over a WebSocket and returns competing quotes sourced from STON.fi's own pools, other TON DEXs, and off-chain RFQ resolvers.&lt;/li&gt;
&lt;li&gt;📦 &lt;strong&gt;&lt;code&gt;@ston-fi/api&lt;/code&gt;&lt;/strong&gt; — the metadata layer. A typed client for STON.fi's public REST API, which is where you get token symbols, decimals, and images without decoding jetton cells yourself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4ti7ov3osbykx4f7prew.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4ti7ov3osbykx4f7prew.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The split matters conceptually: Omniston knows &lt;strong&gt;prices&lt;/strong&gt;, the REST API knows &lt;strong&gt;tokens&lt;/strong&gt;. You need both, and confusing their responsibilities is a common early mistake.&lt;/p&gt;




&lt;h2&gt;
  
  
  📁 Section 1: Project Setup
&lt;/h2&gt;

&lt;p&gt;Start with a clean directory and the minimal dependency set.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;stonfi-quote-viewer &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;stonfi-quote-viewer
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; @ston-fi/omniston-sdk @ston-fi/api
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; typescript tsx @types/node
npx tsc &lt;span class="nt"&gt;--init&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;tsx&lt;/code&gt; lets you run TypeScript directly without a separate build step, which keeps the feedback loop tight while you're iterating.&lt;/p&gt;

&lt;p&gt;Open &lt;code&gt;tsconfig.json&lt;/code&gt; and make sure these are set — the SDK ships ESM and modern types, and mismatched module settings are the single most common setup failure people hit here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ES2022"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ESNext"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"moduleResolution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bundler"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"strict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"esModuleInterop"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"skipLibCheck"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"outDir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dist"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add &lt;code&gt;"type": "module"&lt;/code&gt; to your &lt;code&gt;package.json&lt;/code&gt;, plus a run script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tsx watch src/index.ts"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you skip the &lt;code&gt;"type": "module"&lt;/code&gt; line, you'll hit a &lt;code&gt;Cannot use import statement outside a module&lt;/code&gt; error the first time you run anything. It's a two-second fix that costs people twenty minutes when they don't know to look for it.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔌 Section 2: Connecting to Omniston
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;src/omniston.ts&lt;/code&gt;. This file does one job: produce a configured client instance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Omniston&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@ston-fi/omniston-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PRODUCTION_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wss://omni-ws.ston.fi&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SANDBOX_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wss://omni-ws-sandbox.ston.fi&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;omniston&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Omniston&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OMNISTON_URL&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;SANDBOX_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two deliberate choices here that are worth explaining rather than glossing over.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, the endpoint is environment-driven, not hardcoded.&lt;/strong&gt; Omniston exposes a sandbox WebSocket that mirrors the production API surface exactly, so you can develop against real API shapes without competing for real liquidity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second, the default is sandbox, not production.&lt;/strong&gt; This is the safer direction to fail in. A misconfigured deploy that falls back to sandbox shows you wrong-looking numbers and you investigate. A misconfigured deploy that falls back to &lt;em&gt;production&lt;/em&gt; silently starts doing real things against real liquidity. For a read-only quote viewer the stakes are low, but it's the right habit to build now, before you add a signing step to a later project.&lt;/p&gt;

&lt;p&gt;For a quote viewer specifically you'll probably want production data, since sandbox liquidity isn't representative of real market prices:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;OMNISTON_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;wss://omni-ws.ston.fi npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🏷️ Section 3: Fetching Token Metadata
&lt;/h2&gt;

&lt;p&gt;Before requesting a quote you need to know which tokens you're quoting — and, critically, &lt;strong&gt;how many decimals each one uses.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is where new TON developers most reliably trip. Every amount in the Omniston API is expressed in &lt;strong&gt;base units&lt;/strong&gt;, the smallest indivisible unit of the token. Many TON jettons use 9 decimals, but plenty don't: USDT on TON uses 6. That means "1 USDT" is &lt;code&gt;"1000000"&lt;/code&gt;, while "1 TON" is &lt;code&gt;"1000000000"&lt;/code&gt;. Hardcode 9 decimals everywhere and your USDT amounts will be off by a factor of 1,000 — which, in a quote viewer, looks like a wildly mispriced market rather than an obvious bug.&lt;/p&gt;

&lt;p&gt;The metadata API solves this. Create &lt;code&gt;src/assets.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;StonApiClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;AssetTag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AssetInfoV2&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@ston-fi/api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StonApiClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadAssets&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;AssetInfoV2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;assets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;queryAssets&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;AssetTag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LiquidityVeryHigh&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;AssetTag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LiquidityHigh&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; | &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;byAddress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;AssetInfoV2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;asset&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;assets&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;byAddress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contractAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;byAddress&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;requireAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;assets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;AssetInfoV2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;AssetInfoV2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;asset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;assets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Asset not found in STON.fi metadata: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Filtering by liquidity tags keeps the result set to tokens with meaningful depth, which is what you want for a quote viewer — quoting a token with near-zero liquidity mostly returns noise or no quote at all.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;requireAsset&lt;/code&gt; helper exists because the failure mode of a missing asset is subtle. Without it, an undefined &lt;code&gt;decimals&lt;/code&gt; value propagates silently into your formatting math and produces a number that looks plausible but is wrong. Failing loudly at lookup time is far easier to debug than tracing a bad number backwards through three functions.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔢 Section 4: Handling Amounts Without Breaking Them
&lt;/h2&gt;

&lt;p&gt;Here's a rule that will save you real pain: &lt;strong&gt;never use JavaScript's &lt;code&gt;Number&lt;/code&gt; type for token amounts.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Number&lt;/code&gt; is a 64-bit float with 53 bits of integer precision. Token amounts routinely exceed that. A large balance in base units can silently lose its least-significant digits the moment it passes through a float, and the resulting value will look completely reasonable while being quietly wrong. Use &lt;code&gt;BigInt&lt;/code&gt; for arithmetic and strings for transport.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;src/units.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/** Convert a human-readable amount ("1.5") into base units ("1500000"). */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;toBaseUnits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;decimals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;whole&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fraction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fraction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;decimals&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`Amount "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" has more precision than this token supports (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;decimals&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; decimals)`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paddedFraction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fraction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padEnd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;decimals&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;combined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;whole&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;paddedFraction&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^0+&lt;/span&gt;&lt;span class="se"&gt;(?=\d)&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;combined&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;combined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/** Convert base units ("1500000") into a human-readable amount ("1.5"). */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fromBaseUnits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;decimals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BigInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="nc"&gt;BigInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;decimals&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;whole&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fraction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fraction&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;whole&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fractionString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fraction&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;decimals&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/0+$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;whole&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fractionString&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the explicit throw in &lt;code&gt;toBaseUnits&lt;/code&gt; when a user supplies more decimal places than the token supports. The alternative — silently truncating — produces a quote for an amount the user didn't actually request. For a viewer that's merely confusing; in a tool that eventually signs transactions, it's the kind of quiet mismatch that generates support tickets nobody can reproduce.&lt;/p&gt;




&lt;h2&gt;
  
  
  📡 Section 5: Requesting a Quote
&lt;/h2&gt;

&lt;p&gt;Now the core of the project. Create &lt;code&gt;src/quote.ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First, settlement parameters. These tell Omniston which execution paths you're willing to accept:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;SettlementParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;SwapSettlementParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@ston-fi/omniston-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;settlementParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SettlementParams&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;$case&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;swap&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;maxPriceSlippagePips&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 1%&lt;/span&gt;
        &lt;span class="na"&gt;flexibleIntegratorFee&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;satisfies&lt;/span&gt; &lt;span class="nx"&gt;SwapSettlementParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;$case&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;order&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;maxPriceSlippagePips&lt;/code&gt; is measured in pips — hundredths of a basis point.&lt;/strong&gt; So &lt;code&gt;10_000&lt;/code&gt; means 1%, not 100%, and not 1 basis point. This unit is genuinely easy to get wrong by an order of magnitude, and since a too-loose value doesn't throw an error, it just silently removes your protection. Write the percentage in a comment next to it every single time.&lt;/p&gt;

&lt;p&gt;Including &lt;strong&gt;both&lt;/strong&gt; &lt;code&gt;swap&lt;/code&gt; and &lt;code&gt;order&lt;/code&gt; settlement types is the right default for a viewer. It tells Omniston "quote me the best of either execution path," and the response tells you which one actually won. Swap settlement routes through on-chain AMM pools; order settlement routes through resolvers using signed orders or HTLC escrow, which is often the &lt;em&gt;only&lt;/em&gt; path that returns a quote on thin pairs or exotic routes. Restricting to swap-only means you'll see &lt;code&gt;noQuote&lt;/code&gt; in situations where a quote genuinely existed.&lt;/p&gt;

&lt;p&gt;Now the asset identifiers and the request itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AssetId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;QuoteRequest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@ston-fi/omniston-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;tonJetton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;AssetId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;$case&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ton&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;$case&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jetton&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildQuoteRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;inputAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;outputAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;inputBaseUnits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nx"&gt;QuoteRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;inputAsset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;tonJetton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inputAddress&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;outputAsset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;tonJetton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputAddress&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;$case&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;inputUnits&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inputBaseUnits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;settlementParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The nested &lt;code&gt;AssetId&lt;/code&gt; structure looks verbose for a single-chain app, and it is — but it's the reason the same API shape works for TON jettons, EVM tokens, and whatever gets added later without a breaking change. Wrapping it in a &lt;code&gt;tonJetton()&lt;/code&gt; helper keeps that verbosity in exactly one place.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔄 Section 6: Subscribing to the Live Quote Stream
&lt;/h2&gt;

&lt;p&gt;This is the concept that most distinguishes Omniston from a typical REST price API, and getting it wrong is the most common integration bug.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;requestForQuote()&lt;/code&gt; is not a one-shot request. It's a subscription.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You don't get a quote and finish. You open a stream that stays alive and pushes updated quotes as market conditions change. Treat it like a WebSocket feed, not like a &lt;code&gt;fetch()&lt;/code&gt; call.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;src/index.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;omniston&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./omniston.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;loadAssets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;requireAsset&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./assets.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;buildQuoteRequest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./quote.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;toBaseUnits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fromBaseUnits&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./units.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;USDT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;STON&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;EQA2kCVNwVsil2EM2mB0SkXytxCqQjS4mttjDpnXmwG9T6bO&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;assets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;loadAssets&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inputAsset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;requireAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;assets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;USDT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;outputAsset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;requireAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;assets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;STON&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;humanAmount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;10&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inputBaseUnits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;toBaseUnits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;humanAmount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inputAsset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;decimals&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`Requesting quotes for &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;humanAmount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;inputAsset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; → &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;outputAsset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildQuoteRequest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;inputAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;USDT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;outputAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;STON&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;inputBaseUnits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;omniston&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestForQuote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;$case&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ack&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`📡 RFQ opened — id: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rfqId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;quoteUpdated&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="nf"&gt;renderQuote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inputAsset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;outputAsset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;humanAmount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;noQuote&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`❌ No route available (RFQ &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rfqId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unsubscribed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;🔌 Stream closed by the server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RFQ stream failed:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Walk through what each event actually means, because handling only &lt;code&gt;quoteUpdated&lt;/code&gt; — which is what most first attempts do — leaves your app silent in three situations where it should be telling the user something:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📡 &lt;strong&gt;&lt;code&gt;ack&lt;/code&gt;&lt;/strong&gt; — Omniston received your request and assigned it an &lt;code&gt;rfqId&lt;/code&gt;. This confirms the connection works and gives you an identifier to correlate every later event in this stream. Useful for logging, essential for debugging a stream that produces nothing afterward.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;&lt;code&gt;quoteUpdated&lt;/code&gt;&lt;/strong&gt; — a quote arrived. &lt;strong&gt;This fires repeatedly.&lt;/strong&gt; It is not a completion signal. Each emission is a fresh price reflecting current market conditions, and later ones supersede earlier ones.&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;&lt;code&gt;noQuote&lt;/code&gt;&lt;/strong&gt; — no connected source could fill this request. Common on illiquid pairs or when the requested size exceeds what any resolver will quote. This is a normal outcome, not an error — handle it as information, not as a failure state.&lt;/li&gt;
&lt;li&gt;🔌 &lt;strong&gt;&lt;code&gt;unsubscribed&lt;/code&gt;&lt;/strong&gt; — the server ended the stream. Anything you were displaying is now stale.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Never treat the first &lt;code&gt;quoteUpdated&lt;/code&gt; as final.&lt;/strong&gt; If you cache it and later use it for anything real, you're operating on a price that may be many seconds out of date. Always hold the most recent event in state.&lt;/p&gt;




&lt;h2&gt;
  
  
  🖨️ Section 7: Rendering the Quote
&lt;/h2&gt;

&lt;p&gt;Finally, turn a quote object into something legible. Add &lt;code&gt;renderQuote&lt;/code&gt; to &lt;code&gt;src/index.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isSwapQuote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isOrderQuote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Quote&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@ston-fi/omniston-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AssetInfoV2&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@ston-fi/api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;renderQuote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Quote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;inputAsset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AssetInfoV2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;outputAsset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AssetInfoV2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;humanInput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;settlementKind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;settlementData&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;$case&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unknown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;isSwapQuote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AMM swap&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;isOrderQuote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Resolver order&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;settlementKind&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;─&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;52&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Quote ID     : &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quoteId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Settlement   : &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`You send     : &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;humanInput&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;inputAsset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Updated at   : &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toLocaleTimeString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;─&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;52&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SDK ships type-guard helpers — &lt;code&gt;isSwapQuote&lt;/code&gt;, &lt;code&gt;isOrderQuote&lt;/code&gt;, &lt;code&gt;isHtlcOrderQuote&lt;/code&gt;, and &lt;code&gt;matchQuoteByType&lt;/code&gt; — specifically so you don't hand-roll this branching. Use them. They narrow the type correctly in TypeScript, which means your editor will tell you which fields are actually available on each quote variant instead of letting you access something that's undefined at runtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One honest caveat about output amounts.&lt;/strong&gt; The exact field names carrying the output quantity differ between SDK versions — older releases used &lt;code&gt;offerUnits&lt;/code&gt;/&lt;code&gt;askUnits&lt;/code&gt;, and the &lt;code&gt;v1beta8&lt;/code&gt; surface reorganized parts of the quote structure. Rather than copying a field name from a blog post (including this one) and hoping, log the object once and look:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;quoteUpdated&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it, read the actual shape you're receiving, then write your formatter against that — using &lt;code&gt;fromBaseUnits(rawValue, outputAsset.meta.decimals)&lt;/code&gt; to turn the raw string into something readable. This takes thirty seconds and is strictly more reliable than trusting any secondhand documentation, including current docs, against a pre-1.0 SDK.&lt;/p&gt;

&lt;p&gt;That pre-1.0 status is worth taking seriously in general. &lt;code&gt;@ston-fi/omniston-sdk&lt;/code&gt; is under active development at a major version of zero, which under semver means breaking changes can legitimately land in minor releases. &lt;strong&gt;Pin your version explicitly&lt;/strong&gt; in &lt;code&gt;package.json&lt;/code&gt; rather than trusting a caret range:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@ston-fi/omniston-sdk"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.8.0"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ⚠️ Section 8: Mistakes Worth Avoiding
&lt;/h2&gt;

&lt;p&gt;A consolidated list of the traps this project can fall into, most of which fail silently rather than throwing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔢 &lt;strong&gt;Hardcoding 9 decimals.&lt;/strong&gt; USDT on TON uses 6. Always read &lt;code&gt;decimals&lt;/code&gt; from asset metadata.&lt;/li&gt;
&lt;li&gt;💥 &lt;strong&gt;Using &lt;code&gt;Number&lt;/code&gt; for base units.&lt;/strong&gt; Precision loss past 2^53 produces plausible-looking wrong values. &lt;code&gt;BigInt&lt;/code&gt; and strings only.&lt;/li&gt;
&lt;li&gt;📏 &lt;strong&gt;Misreading pips.&lt;/strong&gt; &lt;code&gt;10_000&lt;/code&gt; is 1%. A wrong value here doesn't error — it just removes protection.&lt;/li&gt;
&lt;li&gt;🔁 &lt;strong&gt;Treating the stream as one-shot.&lt;/strong&gt; Always render from the latest &lt;code&gt;quoteUpdated&lt;/code&gt;, never a cached first one.&lt;/li&gt;
&lt;li&gt;🙈 &lt;strong&gt;Handling only &lt;code&gt;quoteUpdated&lt;/code&gt;.&lt;/strong&gt; Your app goes silent on &lt;code&gt;noQuote&lt;/code&gt; and &lt;code&gt;unsubscribed&lt;/code&gt;, and users conclude it's broken.&lt;/li&gt;
&lt;li&gt;🎯 &lt;strong&gt;Restricting to swap-only settlement&lt;/strong&gt; without reason, then seeing &lt;code&gt;noQuote&lt;/code&gt; on pairs where an order-settled quote existed.&lt;/li&gt;
&lt;li&gt;📌 &lt;strong&gt;Trusting a caret version range&lt;/strong&gt; on a pre-1.0 SDK.&lt;/li&gt;
&lt;li&gt;🔗 &lt;strong&gt;Forgetting to unsubscribe.&lt;/strong&gt; In a long-running app, abandoned subscriptions leak. Keep the subscription handle and call &lt;code&gt;.unsubscribe()&lt;/code&gt; when inputs change or the component unmounts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one deserves a line of code, since it's the one that only bites you later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;omniston&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestForQuote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SIGINT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🚀 Where to Take This Next
&lt;/h2&gt;

&lt;p&gt;You now have a working, read-only foundation. Natural extensions, roughly in order of effort:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A token picker&lt;/strong&gt; — you already load the full asset list; wire it to CLI arguments or a prompt instead of two hardcoded constants.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate comparison over time&lt;/strong&gt; — log each &lt;code&gt;quoteUpdated&lt;/code&gt; with a timestamp and watch how a pair's pricing actually moves. This is genuinely informative about a market's volatility in a way a static screenshot isn't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A price alert&lt;/strong&gt; — trigger a notification when a quote crosses a threshold. Still no wallet required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A web UI&lt;/strong&gt; — swap &lt;code&gt;@ston-fi/omniston-sdk&lt;/code&gt; for &lt;code&gt;@ston-fi/omniston-sdk-react&lt;/code&gt; and the same logic becomes the &lt;code&gt;useRfq()&lt;/code&gt; hook, with loading and error states handled for you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Actual swapping&lt;/strong&gt; — the step you deliberately skipped. Branch on &lt;code&gt;quote.settlementData?.$case&lt;/code&gt;, call &lt;code&gt;tonBuildSwap()&lt;/code&gt; for swap quotes, sign the returned messages with TonConnect, and track the result with &lt;code&gt;swapTrack()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The reason to arrive at that last step &lt;em&gt;after&lt;/em&gt; building this one is that by then you already understand the quote lifecycle, the units, and the event model. Signing becomes the only genuinely new thing you're learning — rather than one unfamiliar piece among five, at the exact moment real funds enter the picture.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔗 Sources &amp;amp; Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;STON.fi Developer Docs — Omniston Node.js SDK (v1beta8): &lt;a href="https://docs.ston.fi/developer-section/omniston/sdk/nodejs" rel="noopener noreferrer"&gt;https://docs.ston.fi/developer-section/omniston/sdk/nodejs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;STON.fi Developer Docs — Omniston React SDK: &lt;a href="https://docs.ston.fi/developer-section/omniston/sdk/react" rel="noopener noreferrer"&gt;https://docs.ston.fi/developer-section/omniston/sdk/react&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;STON.fi Developer Docs — Omniston overview: &lt;a href="https://docs.ston.fi/developer-section/omniston" rel="noopener noreferrer"&gt;https://docs.ston.fi/developer-section/omniston&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Omniston Quickstart Guide (React): &lt;a href="https://docs.ston.fi/developer-section/quickstart/omniston" rel="noopener noreferrer"&gt;https://docs.ston.fi/developer-section/quickstart/omniston&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@ston-fi/omniston-sdk&lt;/code&gt; on npm: &lt;a href="https://www.npmjs.com/package/@ston-fi/omniston-sdk" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@ston-fi/omniston-sdk&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Omniston SDK — GitHub source, CHANGELOG, and example app: &lt;a href="https://github.com/ston-fi/omniston-sdk" rel="noopener noreferrer"&gt;https://github.com/ston-fi/omniston-sdk&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;STON.fi DEX API Reference: &lt;a href="https://docs.ston.fi/developer-section/dex/api/reference" rel="noopener noreferrer"&gt;https://docs.ston.fi/developer-section/dex/api/reference&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This walkthrough reflects the STON.fi and Omniston SDK documentation as of mid-2026 (&lt;code&gt;v1beta8&lt;/code&gt; API surface). The SDK is pre-1.0 and under active development — method signatures, event shapes, and quote field names can change between minor releases. Verify against the current docs at docs.ston.fi and the SDK's CHANGELOG before building anything production-facing, and inspect the live quote object rather than assuming field names from any written guide.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How Do Referral Fees Affect an Omniston Widget Quote?</title>
      <dc:creator>Web3KD</dc:creator>
      <pubDate>Tue, 15 Sep 2026 08:56:37 +0000</pubDate>
      <link>https://dev.to/web3kd/how-do-referral-fees-affect-an-omniston-widget-quote-3gd7</link>
      <guid>https://dev.to/web3kd/how-do-referral-fees-affect-an-omniston-widget-quote-3gd7</guid>
      <description>&lt;h1&gt;
  
  
  How Do Referral Fees Affect an Omniston Widget Quote?
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;STON.fi / Omniston integration guide • Updated September 14, 2026&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Referral fees in the STON.fi Omniston Widget are more than a payout setting for the integrator. Once a referral fee is configured, it becomes part of the request for quote itself — which means the quote shown to the user is calculated with that fee already included in the trade economics.&lt;/p&gt;

&lt;p&gt;That distinction matters more than it might first appear. The fee is not simply added after Omniston finds a price, and it is not a hidden surcharge applied once the user accepts a quote. Omniston receives the referral parameters together with the RFQ, resolvers compete under those conditions, and the resulting quote describes what the trader pays and receives after the relevant fees have already been incorporated into the calculation.&lt;/p&gt;

&lt;p&gt;For anyone embedding the Widget, the practical consequence is direct: changing the referral fee can change the displayed rate, the amount received, the amount required for an exact-output trade, and — in some cases — even which liquidity routes remain competitive or available at all.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💭 The mental model that causes the most confusion here is treating a referral fee like a service charge stapled onto an invoice after the total is calculated. It isn't. It's closer to a constraint handed to the pricing engine before it starts searching — which is why the effect isn't always a clean subtraction.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  ⚙️ How the Widget Passes a Referral Fee Into Omniston
&lt;/h2&gt;

&lt;p&gt;The public Widget configuration exposes two referral-related fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;referrerAddress&lt;/code&gt;&lt;/strong&gt; — the TON wallet address that receives the integrator fee.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;referrerFeeBps&lt;/code&gt;&lt;/strong&gt; — the requested fee in basis points, where 1 basis point equals 0.01%.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The documented programmatic range is &lt;strong&gt;1 to 100 bps&lt;/strong&gt;, or 0.01% to 1%. A value of &lt;code&gt;50&lt;/code&gt; therefore means a 0.5% referral fee.&lt;/p&gt;

&lt;p&gt;In practice, configuring this looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;widget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OmnistonWidget&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;tonconnect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;standalone&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;manifestUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://your-app.example/tonconnect-manifest.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;referrerAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;EQB_yourReferralWallet...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;referrerFeeBps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 0.5%&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the Omniston protocol level, the same idea surfaces in quote requests as &lt;code&gt;referrer_address&lt;/code&gt; and &lt;code&gt;referrer_fee_bps&lt;/code&gt;. Because these values are submitted as part of the RFQ, the resolver is never quoting a fee-free trade and then attaching an integrator charge afterward. It is quoting the trade under the requested fee conditions from the very start of the pricing process.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; a referral fee is part of the RFQ itself. The quote the user sees is therefore already calculated under that fee configuration, rather than receiving an extra referral deduction after pricing.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🔍 The Quote the User Sees Is Already Fee-Aware
&lt;/h2&gt;

&lt;p&gt;Omniston's swap documentation defines these two fields precisely:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Definition&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;bid_units&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The amount of the bid asset the trader must pay, &lt;strong&gt;including all fees&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;ask_units&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The amount of the ask asset the trader receives, &lt;strong&gt;after all fees&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The quote also contains dedicated referral-fee fields, including the referral fee amount and the fee asset it's denominated in.&lt;/p&gt;

&lt;p&gt;This is the single most important concept for understanding the Widget's numbers. If a referral fee is active, the displayed exchange outcome should be treated as a &lt;strong&gt;net quote&lt;/strong&gt; — not as a raw market price still awaiting an additional referral deduction.&lt;/p&gt;

&lt;p&gt;In practice, that means the user should evaluate the quote exactly as displayed: how much is being spent, how much is being received, and what route is being offered. The integrator fee is already baked into the conditions that produced those numbers, which is precisely why there's no "hidden charge" moment later in the flow.&lt;/p&gt;




&lt;h2&gt;
  
  
  ↔️ What Changes for Exact-Input and Exact-Output Trades
&lt;/h2&gt;

&lt;p&gt;Omniston can quote with either the input side or the output side fixed. Referral fees affect these two directions differently from the user's perspective, and the difference is worth understanding separately rather than assuming symmetry.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj38y702h2x9vzpwe9dfv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj38y702h2x9vzpwe9dfv.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Exact input: the user fixes how much to spend
&lt;/h3&gt;

&lt;p&gt;If the user wants to swap a fixed amount of the bid asset, that bid amount represents the &lt;strong&gt;total amount paid, including fees&lt;/strong&gt;. Omniston then competes to maximize the ask amount the user receives under those conditions.&lt;/p&gt;

&lt;p&gt;With a higher referral fee, the resulting output may become less favorable compared with an otherwise identical fee-free request. However — and this is the part that surprises integrators — the difference is not guaranteed to equal the referral percentage exactly. A new RFQ can surface a different resolver, a different pool, a different split route, or a different effective market price altogether.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exact output: the user fixes how much to receive
&lt;/h3&gt;

&lt;p&gt;If the user requests a fixed ask amount, that target represents what the trader wants to receive &lt;strong&gt;after fees&lt;/strong&gt;. The quote therefore has to work backward to determine how much input is required to satisfy that net output.&lt;/p&gt;

&lt;p&gt;In this direction, increasing the referral fee tends to show up as a &lt;strong&gt;higher required bid amount&lt;/strong&gt; rather than a lower displayed ask amount. Again, the result is route-dependent: the fee changes the economic constraints of the quote, and Omniston searches for the best available deal under those revised constraints.&lt;/p&gt;




&lt;h2&gt;
  
  
  📐 Why a 0.5% Fee Doesn't Always Make a Quote Exactly 0.5% Worse
&lt;/h2&gt;

&lt;p&gt;It's tempting to assume a 50 bps referral fee should mechanically make every quote 0.5% worse. That is not how an aggregator behaves.&lt;/p&gt;

&lt;p&gt;The referral rate is one component of the RFQ, but the final quote also depends on liquidity depth, pool fees, price impact, resolver competition, route composition, and which protocols can actually support the requested referral configuration. When the referral fee changes, Omniston can effectively be solving a &lt;strong&gt;different routing problem&lt;/strong&gt; — not the same problem with a percentage shaved off the end.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4fskmw39awue2738sst1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4fskmw39awue2738sst1.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Consider two requests with the same token pair and trade size:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Request&lt;/th&gt;
&lt;th&gt;Referral fee&lt;/th&gt;
&lt;th&gt;What Omniston evaluates&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0 bps&lt;/td&gt;
&lt;td&gt;Best available quote without integrator referral revenue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;B&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;50 bps&lt;/td&gt;
&lt;td&gt;Best available quote that can satisfy a 0.5% referral fee&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The second quote may use the same route with slightly different net economics, or it may select an entirely different route. Therefore, the cleanest way to measure the real user cost of a referral setting is to &lt;strong&gt;compare live quotes&lt;/strong&gt; using the same pair, direction, amount, and market conditions — rather than subtracting the fee percentage from a theoretical spot price and calling it the answer.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚧 Referral Fees Can Affect Route Eligibility
&lt;/h2&gt;

&lt;p&gt;The effect becomes especially visible when comparing STON.fi DEX versions. According to the current Omniston referral-fee documentation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DEX v1&lt;/strong&gt; has a fixed maximum referral fee of &lt;strong&gt;0.1% (10 bps)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DEX v2, DeDust, Tonco, CoffeeSwap, and Escrow&lt;/strong&gt; can support configurable referral fees from &lt;strong&gt;0.01% to 1%&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Suppose an integrator requests a 30 bps fee. A DEX v1 pool simply cannot satisfy that fee as requested. At the protocol level, a strict 30 bps request can therefore &lt;strong&gt;prevent a v1 route from being used at all&lt;/strong&gt; — even if that pool would otherwise offer the strongest underlying price for the trade.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw8s78rx8yymh1jus938y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw8s78rx8yymh1jus938y.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Omniston provides a lower-level &lt;code&gt;flexible_referrer_fee&lt;/code&gt; settlement option for exactly this situation. When enabled, the protocol may reduce the effective referral fee when doing so unlocks a better route for the trader. The documentation gives the concrete example of reducing a 30 bps request down to the 10 bps maximum supported by DEX v1, if that produces a better overall swap rate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One important detail for Widget integrators:&lt;/strong&gt; the current public Widget configuration table documents &lt;code&gt;referrerAddress&lt;/code&gt; and &lt;code&gt;referrerFeeBps&lt;/code&gt;, but it does &lt;strong&gt;not&lt;/strong&gt; list &lt;code&gt;flexible_referrer_fee&lt;/code&gt; as a Widget configuration field. Developers who need explicit control over that settlement behavior should consult the lower-level Omniston SDK or API documentation rather than assuming the Widget exposes the same parameter.&lt;/p&gt;




&lt;h2&gt;
  
  
  🏦 The Payout Mechanism Doesn't Change What the Quote Means
&lt;/h2&gt;

&lt;p&gt;Where the integrator eventually receives the fee depends on the route — but this settlement detail should not be confused with quote calculation itself.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Route&lt;/th&gt;
&lt;th&gt;How the referral fee is paid&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;STON.fi DEX v1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Paid on-chain within the same swap transaction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;STON.fi DEX v2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Accumulates in a dedicated vault, claimed later&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DeDust, Tonco, CoffeeSwap&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Separate fee-vault mechanism for jetton-denominated fees; TON-denominated fees credited directly during the swap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Escrow&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Uses its own fee-vault contracts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These differences matter operationally for the integrator — they affect cash-flow timing and claiming workflows. But the user's quote remains fee-aware regardless. &lt;strong&gt;A fee being stored in a vault rather than transferred immediately does not mean it sits outside the quoted trade economics.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  📊 What an Integrator Should Monitor
&lt;/h2&gt;

&lt;p&gt;Referral revenue is attractive because it lets a product monetize swap flow without building its own execution layer. But the fee level is also a &lt;strong&gt;product decision&lt;/strong&gt; — it changes the economic conditions presented to your users.&lt;/p&gt;

&lt;p&gt;A practical monitoring framework tracks several things together:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Net ask amount for exact-input trades.&lt;/strong&gt; Compare what users actually receive at different referral settings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Required bid amount for exact-output trades.&lt;/strong&gt; Measure how much additional input is needed to preserve the same target output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Route and protocol mix.&lt;/strong&gt; Watch whether higher fees quietly reduce the use of routes that would otherwise be competitive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quote competitiveness.&lt;/strong&gt; Compare your Widget's net quote against a zero-fee or lower-fee control under similar market conditions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conversion behavior.&lt;/strong&gt; A higher fee can increase revenue per completed swap while potentially reducing completion rate if users notice worse pricing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The optimal fee is therefore &lt;strong&gt;not automatically the maximum 100 bps&lt;/strong&gt;. The best setting depends on the balance between monetization and execution quality for the specific audience using your Widget.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧪 A Simple Way to Test Impact Before Choosing a Fee
&lt;/h2&gt;

&lt;p&gt;Before publishing a referral setting, run repeated quote comparisons across the token pairs and trade sizes that actually matter to your users.&lt;/p&gt;

&lt;p&gt;For each test case, request or observe the same trade at several fee levels — for example &lt;strong&gt;0, 10, 30, and 50 bps&lt;/strong&gt; — and record the final bid and ask amounts. Repeat the comparison over time, because routing and liquidity conditions change independently of your configuration.&lt;/p&gt;

&lt;p&gt;Do not judge the result only by the referral percentage. What matters is the &lt;strong&gt;net difference in the actual quote&lt;/strong&gt;. A 30 bps fee that leaves route quality almost unchanged may be far less noticeable to users than a smaller fee that happens to exclude a particularly efficient route for a specific pair.&lt;/p&gt;

&lt;p&gt;If you're working with the lower-level Omniston SDK or API, also test the flexible-referrer-fee behavior whenever your requested rate exceeds 10 bps. That can reveal whether accepting a smaller fee on some trades improves execution enough to increase swap completion — and, counterintuitively, total referral revenue.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 Final Takeaway
&lt;/h2&gt;

&lt;p&gt;Referral fees in the Omniston Widget are part of &lt;strong&gt;quote formation&lt;/strong&gt;, not an afterthought added after pricing. The configured &lt;code&gt;referrerFeeBps&lt;/code&gt; becomes part of the RFQ, and Omniston returns a fee-aware result in which the trader's input is defined &lt;em&gt;including&lt;/em&gt; fees and the expected output is defined &lt;em&gt;after&lt;/em&gt; fees.&lt;/p&gt;

&lt;p&gt;For an exact-input trade, a referral fee can reduce the net amount received. For an exact-output trade, it can increase the amount the trader needs to spend. More importantly, the fee can change routing itself — so the difference between two quotes is rarely a simple percentage calculation.&lt;/p&gt;

&lt;p&gt;That is why referral-fee optimization should be treated as an &lt;strong&gt;execution-quality problem&lt;/strong&gt; as much as a monetization problem. The right question isn't only, &lt;em&gt;"How much can the integrator earn per swap?"&lt;/em&gt; It's also, &lt;em&gt;"What net quote does the user actually receive once Omniston has re-optimized the trade under that fee?"&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  ❓ Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does the user see the referral fee as a separate line item?&lt;/strong&gt;&lt;br&gt;
Not as an additional deduction applied after pricing. The quote's &lt;code&gt;bid_units&lt;/code&gt; already includes all fees and &lt;code&gt;ask_units&lt;/code&gt; already reflects the amount received after fees — though the quote does contain dedicated referral-fee fields describing the fee amount and asset.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why did raising my fee by 20 bps change the output by more (or less) than 20 bps?&lt;/strong&gt;&lt;br&gt;
Because the referral fee is a constraint inside the RFQ, not a post-pricing subtraction. A different fee can lead Omniston to select a different resolver, pool, or split route entirely — so the net difference reflects a re-optimized trade, not just the arithmetic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can a referral fee make a route unavailable?&lt;/strong&gt;&lt;br&gt;
Yes. DEX v1 supports a maximum of 10 bps, so a strict request above that can exclude v1 routes even when they'd otherwise offer the best underlying price. The lower-level &lt;code&gt;flexible_referrer_fee&lt;/code&gt; option exists to let the protocol reduce the fee when doing so unlocks a better route.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is the maximum 100 bps fee the best choice for revenue?&lt;/strong&gt;&lt;br&gt;
Not necessarily. Higher fees increase revenue per completed swap but can worsen displayed pricing and reduce completion rate. Testing across realistic pairs and trade sizes is the only reliable way to find the balance for your specific audience.&lt;/p&gt;




&lt;h2&gt;
  
  
  📚 Sources and References
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;STON.fi — Full Guide &amp;amp; Reference for the Omniston Widget&lt;/strong&gt;: &lt;a href="https://docs.ston.fi/developer-section/widget/widget" rel="noopener noreferrer"&gt;https://docs.ston.fi/developer-section/widget/widget&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;STON.fi — Omniston Referral Fees&lt;/strong&gt;: &lt;a href="https://docs.ston.fi/developer-section/omniston/referral-fees" rel="noopener noreferrer"&gt;https://docs.ston.fi/developer-section/omniston/referral-fees&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;STON.fi — Omniston Swap Overview / Quote Fields&lt;/strong&gt;: &lt;a href="https://docs.ston.fi/developer-section/omniston/swap/overview" rel="noopener noreferrer"&gt;https://docs.ston.fi/developer-section/omniston/swap/overview&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;STON.fi — Omniston Widget GitHub Repository&lt;/strong&gt;: &lt;a href="https://github.com/ston-fi/omniston-widget" rel="noopener noreferrer"&gt;https://github.com/ston-fi/omniston-widget&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;Tags: STON.fi, Omniston, Referral Fees, Widget Integration, RFQ, Quote Formation, TON Blockchain, DeFi Development&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>SDK Integration Walkthrough: Building Your First STONfi Swap Feature</title>
      <dc:creator>Web3KD</dc:creator>
      <pubDate>Sat, 05 Sep 2026 12:53:41 +0000</pubDate>
      <link>https://dev.to/web3kd/sdk-integration-walkthrough-building-your-first-stonfi-swap-feature-49n6</link>
      <guid>https://dev.to/web3kd/sdk-integration-walkthrough-building-your-first-stonfi-swap-feature-49n6</guid>
      <description>&lt;h1&gt;
  
  
  SDK Integration Walkthrough: Building Your First STONfi Swap Feature
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Most SDK documentation shows you the happy path in five clean lines and leaves the actual engineering — the part where quotes expire, wallets reject signatures, and networks hiccup mid-transaction — as an exercise for whoever ships it to production. This is the walkthrough I wish I'd had before my first integration.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I've watched a lot of developers get a STONfi swap working in an afternoon, feel great about it, and then spend the following week discovering everything the happy-path demo quietly skipped. That's not a criticism of the SDK — it's just what happens when documentation optimizes for "does this work" instead of "does this survive contact with real users, real network conditions, and real wallets that sometimes reject things." This walkthrough goes through the same ground a typical quickstart covers, but stays a little longer at each step than a quickstart usually does, specifically at the points where I've seen integrations quietly break in production.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;My take:&lt;/strong&gt; The gap between "my demo works" and "my integration is production-ready" isn't more features. It's almost entirely error handling for cases the demo never triggers, because demos are, by construction, always the happy path.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  🧩 Setting Up and Requesting Your First Quote
&lt;/h2&gt;

&lt;p&gt;Before building anything, you need a client instance connected to STONfi's infrastructure. The basic setup is intentionally minimal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;StonApiClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@ston-fi/api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StonApiClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getQuote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;offerToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;askToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;simulateSwap&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;offerAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;offerToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;askAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;askToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;offerUnits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;slippageTolerance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.01&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 1% — set explicitly, not left to a default&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;slippageTolerance&lt;/code&gt; line deserves more attention than it usually gets in a first pass. Leaving it to whatever the SDK defaults to means your app's behavior under volatile market conditions is whatever some library maintainer decided was reasonable in the abstract, for every possible use case, which is rarely what your specific product actually needs. A wallet app aggregating small casual swaps and a trading terminal handling larger, time-sensitive orders should almost certainly not share the same default — set it explicitly and treat the value as a product decision, not a technical formality you can skip past.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F68a35305g50162unqom0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F68a35305g50162unqom0.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The quote object you get back contains more than just an expected output amount. It carries &lt;code&gt;askUnits&lt;/code&gt; (the optimistic expected output), &lt;code&gt;minAskUnits&lt;/code&gt; (the guaranteed floor, accounting for your slippage tolerance), a route, fee information, and an expiry timestamp. Every one of those fields matters for what comes next — none of them are safe to ignore just because the demo only ever prints &lt;code&gt;askUnits&lt;/code&gt; to the console.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔻 Building the Transaction Without the One Bug That Silently Breaks Slippage Protection
&lt;/h2&gt;

&lt;p&gt;This is the step where I've seen the most integrations quietly ship a real bug that never shows up in testing, because testing rarely includes the specific market conditions that expose it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildSwapTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userWalletAddress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Reject stale quotes before doing anything else&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expiresAt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Quote expired — request a fresh one before building the transaction&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buildSwapTransaction&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;userWalletAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;offerAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offerAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;askAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;askAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;offerUnits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offerUnits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;minAskUnits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;minAskUnits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// NOT quote.askUnits&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The comment on that last line is doing the most important work in this entire snippet. &lt;code&gt;askUnits&lt;/code&gt; is the optimistic number — what you'd get if nothing moved between quote and execution. &lt;code&gt;minAskUnits&lt;/code&gt; is the actual floor your slippage tolerance is supposed to guarantee. Pass the wrong one into transaction construction and your code compiles, your tests pass, your demo works perfectly in a quiet market — and your slippage protection silently does nothing the first time a real price movement happens, because you've told the transaction to accept the optimistic number as if it were the guarantee. Nobody notices until a user reports getting a worse fill than expected during a volatile hour, and by then it's a support ticket instead of a code review comment.&lt;/p&gt;

&lt;p&gt;The expiry check at the top matters just as much, for a less dramatic but equally real reason: submitting a transaction built from a stale quote doesn't fail gracefully in every wallet and every network condition. Checking client-side, before you even attempt to build the transaction, gives you a clean error message to show the user instead of an opaque on-chain rejection they'll have no context for.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚙️ Handling the Wallet Connection and Signature Flow
&lt;/h2&gt;

&lt;p&gt;Getting a transaction object is only useful if you can actually get it signed. This is the step where TON Connect enters the picture, and where a surprising amount of production bugs live in how errors from &lt;em&gt;this specific step&lt;/em&gt; get surfaced to the user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;TonConnectUI&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tonconnect/ui&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tonConnectUI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TonConnectUI&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;manifestUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://yourapp.com/tonconnect-manifest.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;signAndSend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tonConnectUI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendTransaction&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;validUntil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 5-minute signing window&lt;/span&gt;
      &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;submitted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;boc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;boc&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Reject&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rejected_by_user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;catch&lt;/code&gt; block is doing more product work than it looks like at first glance. A user closing the wallet popup without signing and a genuine network or contract-level failure are fundamentally different events — one is a deliberate, informed decision, the other is a technical problem — and they deserve different messages in your UI. Collapsing both into a single generic "Transaction failed" toast is one of the most common ways a perfectly functional integration ends up feeling broken to users, because a user who intentionally declined to sign gets told something went wrong, when nothing actually did.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;validUntil&lt;/code&gt; field is worth setting deliberately rather than accepting whatever default the library ships with, for the same underlying reason the quote expiry mattered earlier: a signing window that's too long increases the odds a user comes back to a stale popup after getting distracted, and a window that's too short creates failures for users who take a normal amount of time to review what they're signing.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔍 Simulating Before You Ever Touch Real Funds
&lt;/h2&gt;

&lt;p&gt;Before shipping any of the above against mainnet, there's a step worth treating as non-negotiable rather than optional: running the full flow against a simulated or testnet environment, deliberately, against every failure case you can construct, not just the happy path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;testFailureCases&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;expired quote&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getQuote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TOKEN_A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TOKEN_B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expiresAt&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;insufficient gas balance&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// wallet funded with the offer token but zero TON for fees&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;getQuote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TOKEN_A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TOKEN_B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;slippage exceeded mid-execution&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// quote requested, then a large counter-trade simulated before signing&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;getQuote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;VOLATILE_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TOKEN_B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}},&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;testCase&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;cases&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Testing: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;testCase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;testCase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;buildSwapTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TEST_WALLET&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn't a complete test suite — it's a sketch of the mindset that matters more than any specific test framework. Each of these cases corresponds to a real failure a real user will eventually trigger, not a hypothetical edge case invented for thoroughness. If your integration's behavior for each of them is "generic error toast," that's a signal to go back to the error-handling step above before considering the integration done, not a acceptable place to stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧭 What Changes When You Move This to Production
&lt;/h2&gt;

&lt;p&gt;A few things stop being optional the moment real users and real funds are involved, beyond what a local test run naturally covers. Rate limiting on quote requests matters — a user rapidly adjusting a trade amount shouldn't fire a new quote request on every keystroke, both for your own API usage and for giving the user a stable number to actually read before it changes again. Idempotency on the submission step matters too — a double-tapped confirm button, common on mobile under lag, shouldn't be able to submit the same transaction twice, which is a UI-layer guard your SDK integration doesn't provide for you automatically.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft2jpcn2c3t6gfewa3ypi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft2jpcn2c3t6gfewa3ypi.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;My take:&lt;/strong&gt; Almost none of the production hardening described here shows up in an SDK's own documentation, and that's not really a gap in the docs — it's inherent to the fact that an SDK can tell you how to call it correctly, but it can't tell you how your specific product should behave when a user does something the happy path never anticipated.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's also worth logging enough context at each step — which quote was used, what the expiry was, what error category was hit — that when a support ticket comes in about a confusing failure, you're not reconstructing what happened from a raw transaction hash and a user's incomplete memory of what they clicked.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧭 Conclusion
&lt;/h2&gt;

&lt;p&gt;A working STONfi swap integration and a production-ready one are separated less by code volume than by how many of the cases above got deliberate attention instead of getting left as whatever the SDK happened to do by default. Reading &lt;code&gt;minAskUnits&lt;/code&gt; instead of &lt;code&gt;askUnits&lt;/code&gt;, checking quote expiry before building a transaction, distinguishing a user's rejection from an actual failure, and testing against constructed failure cases rather than only the happy path — none of this is exotic engineering. It's the unglamorous difference between a demo that works once, in a quiet market, on a good network day, and an integration that keeps behaving correctly on the day none of those three things are true.&lt;/p&gt;

&lt;h2&gt;
  
  
  ❓ Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Why does using &lt;code&gt;askUnits&lt;/code&gt; instead of &lt;code&gt;minAskUnits&lt;/code&gt; matter if the quote already accounted for slippage?&lt;/strong&gt;&lt;br&gt;
The quote calculates both values, but only &lt;code&gt;minAskUnits&lt;/code&gt; is the enforced floor. Passing &lt;code&gt;askUnits&lt;/code&gt; into the transaction means no real floor is being enforced at all, regardless of what slippage tolerance you originally requested.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should slippage tolerance ever be left at an SDK's default value?&lt;/strong&gt;&lt;br&gt;
It's better treated as a product decision made deliberately for your specific use case — a casual small-swap app and a professional trading interface have different needs, and a shared default rarely fits both well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is checking quote expiry client-side actually necessary if the chain will reject a stale transaction anyway?&lt;/strong&gt;&lt;br&gt;
Yes — a clean client-side check lets you show the user a clear, immediate message and offer a fresh quote, instead of relying on an on-chain rejection that arrives later with no context for what to do next.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much failure-case testing is actually enough before shipping?&lt;/strong&gt;&lt;br&gt;
At minimum, every case a user is likely to trigger in normal use — expired quotes, rejected signatures, insufficient gas, and slippage exceeded mid-execution — should have an explicit, deliberate behavior, not a generic fallback.&lt;/p&gt;




&lt;h2&gt;
  
  
  📚 Sources and Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/ston-fi/sdk" rel="noopener noreferrer"&gt;STONfi SDK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ston-fi/api" rel="noopener noreferrer"&gt;STONfi API Client&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.ston.fi" rel="noopener noreferrer"&gt;STONfi Developer Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.ton.org/develop/dapps/ton-connect/overview" rel="noopener noreferrer"&gt;TON Connect Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Tags: STONfi, SDK, TON Connect, Developers, TON Blockchain, DeFi, Web3&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: Official STONfi Ambassador.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>cryptocurrency</category>
      <category>ai</category>
    </item>
    <item>
      <title>Quote Expiration: Why DeFi Prices Cannot Be Treated Like Static API Responses</title>
      <dc:creator>Web3KD</dc:creator>
      <pubDate>Fri, 04 Sep 2026 14:38:03 +0000</pubDate>
      <link>https://dev.to/web3kd/quote-expiration-why-defi-prices-cannot-be-treated-like-static-api-responses-1dcg</link>
      <guid>https://dev.to/web3kd/quote-expiration-why-defi-prices-cannot-be-treated-like-static-api-responses-1dcg</guid>
      <description>&lt;h1&gt;
  
  
  Quote Expiration: Why DeFi Prices Cannot Be Treated Like Static API Responses
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Cache a weather API response for thirty seconds and nothing bad happens. Cache a swap quote for thirty seconds and you can end up building a transaction against a price that's no longer true. The difference isn't a technicality — it changes how the entire integration has to be built.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Most developers coming into DeFi from traditional web development carry a mental model that quietly breaks the first time they build a swap feature: a price is data, and data can be fetched, stored, and reused for a little while before it needs refreshing. A product listing, a currency conversion rate, a weather forecast — all reasonably treated as short-lived but essentially static once returned. A DeFi quote looks like the same shape of object — a JSON payload with a number in it — and that resemblance is exactly what causes bugs. It isn't a cacheable fact. It's a live, competitive, time-boxed offer from a specific counterparty, and Omniston's own API is built around that distinction explicitly rather than leaving it implicit.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💭 The bug I've seen most often isn't exotic — it's a developer calling a quote endpoint once, storing the number in state, and building the transaction from that stored value minutes later. Nothing in a REST-shaped response warns you not to do that. The protocol has to make the time-boxed nature structural, not just documented.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  ✅ Key Takeaways
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F86mmrm28f4eix4xwkkgb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F86mmrm28f4eix4xwkkgb.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A DeFi quote is a live offer from a specific resolver or pool state, not a fetched fact — it degrades in accuracy the moment it's issued.&lt;/li&gt;
&lt;li&gt;Omniston models this by returning quotes as a subscribable RFQ stream, not a single request/response call, so re-quoting is the default behavior, not an opt-in refresh.&lt;/li&gt;
&lt;li&gt;Quotes carry explicit time-boxing fields — validity windows and trade-start deadlines — that a transaction must respect to guarantee the quoted price.&lt;/li&gt;
&lt;li&gt;Treating quote expiration as a UI afterthought (a toast that says "price changed") produces worse UX than designing the interface around expiration as an expected, routine state from the start.&lt;/li&gt;
&lt;li&gt;Building a transaction from a stale quote isn't just risky — it can fail outright, since expired quotes aren't meant to settle at all.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🌐 Why the REST Mental Model Breaks Here
&lt;/h2&gt;

&lt;p&gt;A typical REST API response represents something that was true at request time and stays reasonably true for a while — a product's list price, a city's current temperature. The implicit contract is: fetch once, trust for some duration, refetch when you need fresher data. Nothing about that contract requires the client to actively track an expiration deadline as part of correctness — staleness is usually a UX nuisance, not a source of financial loss.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftl68buuhl838c9p6f199.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftl68buuhl838c9p6f199.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A swap quote breaks that contract in a specific, structural way: it represents a price a counterparty — a pool's current reserves, or a resolver's live inventory — is willing to honor &lt;em&gt;right now&lt;/em&gt;, and that willingness has a real, finite shelf life measured in seconds, not minutes. Unlike a stale weather reading, a stale quote doesn't just become "a bit inaccurate" — building and submitting a transaction against it can fail outright, or in worse designs, execute at a materially different price than the user actually saw and approved.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔄 How Omniston Encodes This Structurally: A Stream, Not a Call
&lt;/h2&gt;

&lt;p&gt;Rather than exposing quotes as a single &lt;code&gt;GET /quote&lt;/code&gt; request, Omniston's &lt;code&gt;requestForQuote()&lt;/code&gt; returns an observable stream that a client subscribes to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Omniston&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Blockchain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SettlementMethod&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@ston-fi/omniston-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;omniston&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Omniston&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;apiUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wss://omni-ws.ston.fi&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;quoteStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;omniston&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestForQuote&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;settlementMethods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;SettlementMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SETTLEMENT_METHOD_SWAP&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;offerAssetAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;blockchain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Blockchain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TON&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// USDT&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;askAssetAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;blockchain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Blockchain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TON&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;EQA2kCVNwVsil2EM2mB0SkXytxCqQjS4mttjDpnXmwG9T6bO&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// STON&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;offerUnits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1000000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// 1 USDT&lt;/span&gt;
  &lt;span class="na"&gt;settlementParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;maxPriceSlippageBps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 5% max slippage&lt;/span&gt;
    &lt;span class="na"&gt;maxOutgoingMessages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;quoteStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;quoteEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// A fresh quoteEvent can arrive multiple times over the stream's lifetime —&lt;/span&gt;
  &lt;span class="c1"&gt;// resolvers and pools re-quote as conditions change, not just once&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a deliberate design choice, not an incidental one. A WebSocket-backed stream means the &lt;em&gt;default&lt;/em&gt; behavior is continuous re-quoting as market conditions shift, rather than a client having to remember to poll again. An integration that subscribes and simply keeps the most recent event is automatically working with a reasonably fresh price, without needing to build its own refresh-timer logic on top of a request/response API.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⏱ The Fields That Make Expiration Explicit, Not Implicit
&lt;/h2&gt;

&lt;p&gt;Underneath the SDK's convenience layer, Omniston's protocol-level messages carry explicit timing fields that turn "this quote is time-limited" from a documentation note into something a client can programmatically check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight protobuf"&gt;&lt;code&gt;&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;QuoteRequestedEvent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;rfq_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                  &lt;span class="c1"&gt;// Quote request ID&lt;/span&gt;
  &lt;span class="kt"&gt;sint64&lt;/span&gt; &lt;span class="na"&gt;request_timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;       &lt;span class="c1"&gt;// When the request was made&lt;/span&gt;
  &lt;span class="kt"&gt;sint64&lt;/span&gt; &lt;span class="na"&gt;quote_validity_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// How long the quote should remain valid&lt;/span&gt;
  &lt;span class="kt"&gt;sint64&lt;/span&gt; &lt;span class="na"&gt;resolve_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;         &lt;span class="c1"&gt;// Max time allowed to complete the trade&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;UpdateQuoteRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;rfq_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;uint64&lt;/span&gt; &lt;span class="na"&gt;trade_start_deadline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// Quote expiration timestamp&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;quote_validity_timeout&lt;/code&gt; and &lt;code&gt;trade_start_deadline&lt;/code&gt; aren't cosmetic metadata — they're the actual contract a resolver is offering: &lt;em&gt;this price, if and only if execution starts before this deadline.&lt;/em&gt; A transaction built and submitted after that deadline isn't just "risking a worse price" the way slippage tolerance handles ordinary market movement — it's outside the terms of the quote entirely, and settlement logic is built to reject or fail it rather than silently execute at a different rate.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧩 What This Looks Like When Actually Building the Transaction
&lt;/h2&gt;

&lt;p&gt;Because the quote is time-boxed, building a transaction from it means carrying that deadline forward into the transaction itself — not just displaying a price and hoping the user confirms quickly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Conceptual shape — building a transaction needs to reference&lt;/span&gt;
&lt;span class="c1"&gt;// both the specific quote and the window it's valid within&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;omniston&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buildTransfer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                 &lt;span class="c1"&gt;// the exact quote object from the stream above&lt;/span&gt;
  &lt;span class="nx"&gt;sourceAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;destinationAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// The settlement layer enforces the quote's own deadline —&lt;/span&gt;
  &lt;span class="c1"&gt;// a transaction submitted after trade_start_deadline is expected to fail&lt;/span&gt;
  &lt;span class="c1"&gt;// rather than silently settling at a different price&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important design principle here: the deadline isn't something the client application invents on its own (like an arbitrary "5 minute cache" rule borrowed from REST habits) — it's a value the resolver itself set when it made the offer, carried through the quote object, and enforced at settlement. A client can't extend it by simply waiting longer or retrying with the same stale data.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎨 The UX Problem This Actually Creates
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqdymor6o5t9bh8bx09mr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqdymor6o5t9bh8bx09mr.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Handling expiration correctly at the protocol level solves half the problem. The other half is interface design, and it's where a lot of otherwise well-built integrations still fall short:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Treating expiration as an edge case, not a routine state.&lt;/strong&gt; If a user takes 15 seconds to review a quote before confirming, hitting an expired quote shouldn't feel like an error state — it should feel like a normal, expected part of using the app, handled by quietly re-quoting rather than surfacing a raw failure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not showing any indication that a quote is time-limited at all.&lt;/strong&gt; A price displayed with no visual cue that it's a live, decaying offer trains users to treat it like a static number — the same misconception that causes the caching bug in the first place, just moved from the developer to the end user.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blocking confirmation entirely without a graceful re-quote path.&lt;/strong&gt; The better pattern is subscribing to the stream continuously in the background so the displayed price updates itself, and confirmation simply uses whatever the most recent valid quote happens to be at the moment of the click.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;💭 The best version of this UX is almost invisible: the number on screen just quietly stays current, and the user never consciously notices that "quote expiration" was ever a problem being solved underneath. The worst version throws an error message after the user already clicked confirm.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  🛡 Why This Matters More for Cross-Chain and RFQ-Sourced Quotes
&lt;/h2&gt;

&lt;p&gt;Expiration windows aren't uniform across every kind of quote. A quote priced against a deep, stable AMM pool tends to have more forgiving timing, since the underlying reserves move relatively slowly. A quote sourced from an RFQ resolver — especially for a cross-chain trade settling through paired HTLCs — is a live commitment from a specific market maker, often with a tighter validity window, since the resolver is exposing its own capital to price risk for the duration of that offer. Cross-chain flows add a further wrinkle: &lt;code&gt;resolve_timeout&lt;/code&gt; governs not just how long the quote is valid to &lt;em&gt;start&lt;/em&gt;, but how long the overall trade has to &lt;em&gt;complete&lt;/em&gt; across both chains before the settlement window closes — a distinctly different timing concern from same-chain execution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiv7bwr0bdmrn9zfgcv20.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiv7bwr0bdmrn9zfgcv20.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An integration that treats every quote as having the same generous shelf life, regardless of settlement method, is likely to either annoy users with unnecessarily aggressive re-quoting on stable pairs, or — worse — attempt to execute stale RFQ-sourced quotes that were never going to be honored past their deadline.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧭 Conclusion
&lt;/h2&gt;

&lt;p&gt;Quote expiration isn't a quirky edge case bolted onto DeFi APIs — it's a direct consequence of what a quote actually represents: a live, time-boxed commitment from a specific counterparty, not a cacheable fact about the world. Omniston's design reflects that at the protocol level, with a subscribable RFQ stream in place of a single request/response call, and explicit validity and deadline fields carried through to settlement rather than left to client-side assumption. The remaining responsibility sits with whoever builds the interface on top: designing around expiration as a routine, invisible background process, not an error state the user discovers after the fact.&lt;/p&gt;

&lt;h2&gt;
  
  
  ❓ Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Can a client just increase how long it waits before treating a quote as stale?&lt;/strong&gt;&lt;br&gt;
No — the validity window and trade-start deadline are set by the resolver or protocol issuing the quote, not by the client. A client can request a fresh quote at any time, but it can't extend the validity of an existing one past what was actually offered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does every quote on Omniston expire at the same rate?&lt;/strong&gt;&lt;br&gt;
No. Quotes sourced from a deep, stable AMM pool tend to tolerate slightly more delay than RFQ-sourced quotes from an individual resolver, since the resolver is exposing its own capital to price risk for as long as the quote remains valid.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens if a transaction is submitted after a quote's deadline has passed?&lt;/strong&gt;&lt;br&gt;
It's expected to fail at settlement rather than silently execute at a different price — the deadline is enforced as part of the quote's terms, not treated as a soft suggestion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is subscribing to a quote stream instead of polling actually necessary, or just a nice-to-have?&lt;/strong&gt;&lt;br&gt;
It's closer to necessary for correctness. Polling on a fixed interval risks either showing stale prices between polls or hammering the API with unnecessary requests; a subscription model delivers updates exactly when the underlying price actually changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  📚 Sources and Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Omniston Node.js SDK&lt;/strong&gt; — &lt;code&gt;requestForQuote&lt;/code&gt; as an observable RFQ stream: &lt;a href="https://docs.ston.fi/developer-section/omniston/omniston-nodejs" rel="noopener noreferrer"&gt;https://docs.ston.fi/developer-section/omniston/omniston-nodejs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How to Become a Resolver&lt;/strong&gt; — Protocol-level fields including &lt;code&gt;quote_validity_timeout&lt;/code&gt; and &lt;code&gt;trade_start_deadline&lt;/code&gt;: &lt;a href="https://docs.ston.fi/developer-section/omniston/resolvers/guide" rel="noopener noreferrer"&gt;https://docs.ston.fi/developer-section/omniston/resolvers/guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Omniston Protocol Overview&lt;/strong&gt; — Settlement methods and quote lifecycle: &lt;a href="https://docs.ston.fi/developer-section/omniston" rel="noopener noreferrer"&gt;https://docs.ston.fi/developer-section/omniston&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Omniston React SDK&lt;/strong&gt; — &lt;code&gt;useTrackTrade&lt;/code&gt; and quote-to-trade flow: &lt;a href="https://docs.ston.fi/developer-section/omniston/omniston-react" rel="noopener noreferrer"&gt;https://docs.ston.fi/developer-section/omniston/omniston-react&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Tags: STON.fi, Omniston, DeFi UX, Quote Expiration, RFQ, TON Blockchain, Developer Tools, Slippage&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Cross-Chain Routing: Comparing DEX Liquidity and RFQ Liquidity</title>
      <dc:creator>Web3KD</dc:creator>
      <pubDate>Wed, 26 Aug 2026 09:23:34 +0000</pubDate>
      <link>https://dev.to/web3kd/cross-chain-routing-comparing-dex-liquidity-and-rfq-liquidity-2c27</link>
      <guid>https://dev.to/web3kd/cross-chain-routing-comparing-dex-liquidity-and-rfq-liquidity-2c27</guid>
      <description>&lt;h1&gt;
  
  
  Cross-Chain Routing: Comparing DEX Liquidity and RFQ Liquidity
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Two completely different sourcing models sit behind the same "swap" button — one prices against on-chain reserves, the other prices against a competing network of market makers. Cross-chain routing is where the difference between them stops being academic and starts being the whole system.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Same-chain routing gets away with a simplification that cross-chain routing can't: on one chain, "check the pools" and "get a price" are almost the same operation. The moment a route crosses from TON to an EVM chain or vice versa, that simplification breaks. There's no single shared pool spanning both chains — there can't be, since two independent blockchains have no native way to verify each other's state. Something else has to source the liquidity and guarantee settlement, and that's where the split between DEX-pool liquidity and RFQ-sourced liquidity stops being a footnote and becomes the core design decision.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg03du7v7lh6fkyfd3igf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg03du7v7lh6fkyfd3igf.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This piece walks through both models concretely — what each one actually is, how Omniston's SDK exposes them as distinct settlement branches in code, and why cross-chain routing in particular tends to lean on RFQ far more than same-chain routing does.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💭 My honest take: people often describe RFQ liquidity as "the cross-chain version of an AMM pool," as if it's just a variant of the same idea. It isn't. A pool is a formula anyone can query. RFQ is a live negotiation with a counterparty who has to actually show up and deliver. Cross-chain routing depends on that distinction more than any other part of the stack.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  ✅ Key Takeaways
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgjvbh6x9l0qx1stcf0eg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgjvbh6x9l0qx1stcf0eg.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DEX pool liquidity is fully on-chain and formula-priced; it works well within a single chain but has no native way to span two independent chains.&lt;/li&gt;
&lt;li&gt;RFQ liquidity is sourced from resolvers — market makers with real capital who quote and then have to deliver — making it viable across chains where no shared pool can exist.&lt;/li&gt;
&lt;li&gt;Cross-chain settlement in Omniston is handled through paired HTLCs, not through a pool's reserves being adjusted on both sides.&lt;/li&gt;
&lt;li&gt;Omniston's &lt;code&gt;settlementParams&lt;/code&gt; API lets an integration explicitly choose swap-only, order-only, or both — a direct, code-level expression of the DEX-vs-RFQ choice.&lt;/li&gt;
&lt;li&gt;The trade-off is consistent: DEX liquidity is trustless but chain-bound; RFQ liquidity crosses chains but depends on active, capitalized resolvers actually being available.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🏊 DEX Liquidity: What It Is and Where It Stops Working
&lt;/h2&gt;

&lt;p&gt;A DEX pool prices a trade against its own on-chain reserves using a formula — commonly a constant-product model, x·y = k. Anyone can query the current reserves, anyone can predict the resulting price, and settlement is a single atomic transaction on one chain. This works because both sides of the trade live in the same execution environment, with the same finality guarantees, updated by the same state.&lt;/p&gt;

&lt;p&gt;That last part is exactly what breaks across chains. TON's state and, say, an EVM chain's state aren't mutually visible or verifiable to each other in real time. There's no shared pool that could sit "between" them, because a pool needs a single source of truth for its reserves, and two independent blockchains don't share one. A DEX pool model, taken as-is, simply has no native answer to the question "how do I settle half of this trade on a chain the pool doesn't live on?"&lt;/p&gt;

&lt;h2&gt;
  
  
  🤝 RFQ Liquidity: Solving the Problem a Pool Can't
&lt;/h2&gt;

&lt;p&gt;RFQ — Request for Quote — liquidity works differently from the ground up. Instead of pricing against a formula anyone can query, a swap request is broadcast to a network of resolvers: market makers who hold their own capital, often on multiple chains simultaneously, and respond with a live, competitive quote. The resolver isn't reading a shared pool — it's offering to fulfill the trade from its own inventory, wherever that inventory happens to sit.&lt;/p&gt;

&lt;p&gt;This is precisely the piece that makes cross-chain routing possible at all. A resolver with capital on both TON and an EVM chain can quote a trade that starts on one and settles on the other, because it isn't constrained by needing a single, mutually-visible pool. The trade-off is equally direct: RFQ liquidity requires an actual, willing, sufficiently capitalized resolver to exist for that specific pair and route. No resolver, no quote — where a DEX pool at least always returns &lt;em&gt;some&lt;/em&gt; price, however poor, an empty RFQ market returns nothing at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚙️ How Settlement Actually Differs Underneath
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmllme1eh8k0vh8sj5ztn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmllme1eh8k0vh8sj5ztn.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Same-Chain: One Formula, One Transaction
&lt;/h3&gt;

&lt;p&gt;On a single chain, whether the quote came from a pool or a resolver, settlement can complete as one atomic transaction — the trade either fully executes or fully reverts, and both sides are visible to the same chain's consensus at the same moment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cross-Chain: Paired HTLCs, Not a Shared Pool
&lt;/h3&gt;

&lt;p&gt;Cross-chain settlement in Omniston works through Hashed Timelock Contracts (HTLCs) — paired, not shared:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The trader's asset locks into an HTLC on the source chain, tied to a cryptographic secret.&lt;/li&gt;
&lt;li&gt;The winning resolver locks the corresponding destination asset into a mirrored HTLC on the destination chain, tied to the same secret.&lt;/li&gt;
&lt;li&gt;Once the secret is revealed, both HTLCs unlock together — the trader receives the destination asset, the resolver receives the source asset.&lt;/li&gt;
&lt;li&gt;If the secret is never revealed before the timelock expires, both sides are refunded instead — no scenario exists where one side pays and the other doesn't.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the mechanical reason cross-chain routing leans on RFQ so heavily: a resolver is the counterparty that actually locks real destination-chain assets into that second HTLC. A pool's formula has no equivalent action it can take on a chain it doesn't live on.&lt;/p&gt;

&lt;h2&gt;
  
  
  💻 Choosing Between Them in Code
&lt;/h2&gt;

&lt;p&gt;Omniston's v1beta8 API makes this choice explicit rather than implicit — an integration decides upfront which settlement branches it wants to allow, via &lt;code&gt;settlementParams&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Swap-Only: Classic Pool-Style Settlement
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SettlementParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SwapSettlementParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@ston-fi/omniston-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;swapOnlySettlementParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SettlementParams&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;$case&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;swap&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;maxPriceSlippagePips&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 1%&lt;/span&gt;
        &lt;span class="na"&gt;flexibleIntegratorFee&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;satisfies&lt;/span&gt; &lt;span class="nx"&gt;SwapSettlementParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This branch is what you'd reach for on a simple, same-chain TON flow — a single atomic transaction settled directly, the closest analog to a traditional pool-based swap.&lt;/p&gt;

&lt;h3&gt;
  
  
  Order-Only: RFQ-Backed, HTLC-Settled
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SettlementParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;OrderSettlementParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@ston-fi/omniston-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;orderOnlySettlementParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SettlementParams&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;$case&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;order&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="nx"&gt;satisfies&lt;/span&gt; &lt;span class="nx"&gt;OrderSettlementParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This branch is for applications built specifically around signed orders or HTLC flows — exactly the shape cross-chain routing needs, since it's resolver-fulfilled rather than pool-settled.&lt;/p&gt;

&lt;h3&gt;
  
  
  Letting Omniston Decide: Requesting Both
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;swapAndOrderSettlementParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SettlementParams&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;swapOnlySettlementParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;orderOnlySettlementParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;quoteStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;omniston&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestForQuote&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;settlementParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;swapAndOrderSettlementParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;bidAssetAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;blockchain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sourceChain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;offerAssetAddress&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;askAssetAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;blockchain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;destinationChain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;askAssetAddress&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;bidUnits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;offerAmount&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;quoteStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;quoteEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// quoteEvent.settlementData?.$case tells you which branch actually won —&lt;/span&gt;
  &lt;span class="c1"&gt;// "swap" (pool-style) or "order" (resolver/HTLC-based cross-chain)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Requesting both settlement methods and branching on &lt;code&gt;quote.settlementData?.$case&lt;/code&gt; is the practical, code-level version of "compare DEX liquidity and RFQ liquidity, then use whichever actually won" — the decision isn't made by the integrator ahead of time; it's made per-request, based on what's actually available.&lt;/p&gt;

&lt;h3&gt;
  
  
  Disclosing the HTLC Secret for a Cross-Chain Order
&lt;/h3&gt;

&lt;p&gt;For order-flow settlement where the application manages the secret itself, that secret needs to be disclosed once execution is ready to complete:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;disclosure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;omniston&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;discloseHtlcSecret&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;quoteId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quoteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;htlcSecret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// Disclosing the secret is what allows both paired HTLCs — source and&lt;/span&gt;
&lt;span class="c1"&gt;// destination — to unlock together, completing the atomic settlement&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This step has no equivalent at all in pool-based swap settlement — it only exists because cross-chain settlement is built on the shared-secret mechanics of paired HTLCs, not on adjusting a single pool's reserves.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚖️ Side-by-Side: What Actually Differs
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;DEX Pool Liquidity&lt;/th&gt;
&lt;th&gt;RFQ Liquidity&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Price source&lt;/td&gt;
&lt;td&gt;On-chain formula (e.g., x·y=k)&lt;/td&gt;
&lt;td&gt;Live quote from competing resolvers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Works natively cross-chain&lt;/td&gt;
&lt;td&gt;No — bound to a single chain's state&lt;/td&gt;
&lt;td&gt;Yes — resolvers can hold capital across chains&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Settlement mechanism&lt;/td&gt;
&lt;td&gt;Single atomic on-chain transaction&lt;/td&gt;
&lt;td&gt;Paired HTLCs, unlocked by a shared secret&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Availability&lt;/td&gt;
&lt;td&gt;Always returns a price if reserves exist&lt;/td&gt;
&lt;td&gt;Depends on active, willing resolvers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trust model&lt;/td&gt;
&lt;td&gt;Formula and code, fully on-chain&lt;/td&gt;
&lt;td&gt;Resolver must actually deliver, enforced by HTLC refund logic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Omniston API branch&lt;/td&gt;
&lt;td&gt;&lt;code&gt;settlementParams: swap&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;settlementParams: order&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  🧩 Why Cross-Chain Routing Leans Toward RFQ by Default
&lt;/h2&gt;

&lt;p&gt;None of this means DEX-pool liquidity is obsolete — for same-chain trades, it remains simple, fully transparent, and doesn't depend on a third party choosing to participate. But cross-chain routing specifically needs a counterparty capable of acting on the destination chain, and a pool's formula has no way to do that on a chain it doesn't exist on. RFQ liquidity isn't cross-chain routing's "alternative" option — it's closer to the only mechanism that structurally can work once two independent chains are involved, which is exactly why Omniston's cross-chain flows route through the order/HTLC branch rather than the swap branch.&lt;/p&gt;

&lt;h2&gt;
  
  
  🛠 Practical Considerations for Developers Building on Top of This
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8g4nag748malyxnkq023.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8g4nag748malyxnkq023.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Choosing between (or combining) these two liquidity sources isn't purely theoretical once you're actually integrating against Omniston. A few things matter in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Latency expectations differ structurally.&lt;/strong&gt; A pool-based swap settles in one transaction with one chain's finality time. An HTLC-based cross-chain order inherently involves two separate on-chain confirmations — source-chain lock, then destination-chain lock — plus secret disclosure, so it's reasonable to design UI and status handling around a longer, multi-step timeline rather than assuming cross-chain settlement feels as instant as a same-chain swap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;requestForQuote()&lt;/code&gt; is a live stream, not a one-shot call.&lt;/strong&gt; Because it can emit multiple &lt;code&gt;quoteUpdated&lt;/code&gt; events over time as resolvers and pools update their pricing, an integration needs to treat it as an ongoing subscription — grabbing the first event and discarding the stream throws away exactly the competitive re-quoting that makes RFQ liquidity useful in the first place.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Order state needs to be restorable, not just streamed.&lt;/strong&gt; For cross-chain orders specifically, Omniston exposes &lt;code&gt;orderGetActive()&lt;/code&gt; as a regular request-response call so an application can restore or display existing order state — useful if a user closes a tab mid-settlement and needs to see where a paired-HTLC order actually stands when they come back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gas and fee handling differs by branch.&lt;/strong&gt; A swap-only flow can lean on &lt;code&gt;flexibleIntegratorFee&lt;/code&gt; and slippage settings tuned for a single-chain transaction. An order flow tied to HTLC settlement introduces separate considerations on both the source and destination chain, since each leg is its own on-chain action with its own gas cost.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this changes the core architectural point — it just means the DEX-vs-RFQ choice shows up in concrete engineering decisions, not only in how a trade gets priced.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧭 Conclusion
&lt;/h2&gt;

&lt;p&gt;DEX liquidity and RFQ liquidity aren't two flavors of the same thing — they're different answers to different questions. A pool answers "what's the price, given these reserves, on this chain, right now?" A resolver answers "will someone actually deliver the other side of this trade, including on a chain the pool can't reach?" Same-chain routing can often get away with only the first question. Cross-chain routing cannot, and Omniston's split between swap and order settlement in its own API is a direct, honest reflection of that fact — not a stylistic API choice, but a structural one.&lt;/p&gt;

&lt;h2&gt;
  
  
  ❓ Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Can a standard AMM pool ever settle a cross-chain trade on its own?&lt;/strong&gt;&lt;br&gt;
No. A pool's price and settlement both depend on a single chain's shared, mutually-visible state. Since two independent blockchains don't share that state, a pool has no native mechanism to lock or release assets on a chain it doesn't live on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is RFQ liquidity less trustworthy than a pool since it depends on a resolver showing up?&lt;/strong&gt;&lt;br&gt;
It's a different trust model, not a strictly weaker one. The resolver doesn't need to be trusted directly — HTLC-based settlement enforces that either both sides complete or both sides refund, so a resolver failing to deliver results in your funds returning, not being lost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does Omniston let an integration request both swap and order settlement at once?&lt;/strong&gt;&lt;br&gt;
Because the best available option can vary per request — a same-chain trade might have excellent pool liquidity, while a cross-chain trade structurally requires the order/HTLC branch. Requesting both and branching on the returned &lt;code&gt;$case&lt;/code&gt; lets the protocol pick the right mechanism per trade instead of the integrator guessing upfront.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens if no resolver is willing to quote a specific cross-chain pair?&lt;/strong&gt;&lt;br&gt;
The RFQ simply returns no viable quote for that route at that time. Unlike a pool, which will always return some price if reserves exist (however poor), RFQ liquidity depends entirely on active resolver participation for that specific pair and direction.&lt;/p&gt;




&lt;h2&gt;
  
  
  📚 Sources and Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Omniston Protocol Overview&lt;/strong&gt; — Cross-chain settlement architecture and the v1beta8 API model: &lt;a href="https://docs.ston.fi/developer-section/omniston" rel="noopener noreferrer"&gt;https://docs.ston.fi/developer-section/omniston&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Omniston Node.js SDK&lt;/strong&gt; — &lt;code&gt;settlementParams&lt;/code&gt;, swap vs. order flow, and HTLC secret disclosure in code: &lt;a href="https://docs.ston.fi/developer-section/omniston/sdk/nodejs" rel="noopener noreferrer"&gt;https://docs.ston.fi/developer-section/omniston/sdk/nodejs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-chain portfolio rebalancing: how HTLC and RFQ swaps work for DeFi users&lt;/strong&gt; — STON.fi Blog, on combining RFQ discovery with paired-HTLC settlement: &lt;a href="https://blog.ston.fi/cross-chain-portfolio-rebalancing-how-htlc-and-rfq-swaps-work-for-defi-users/" rel="noopener noreferrer"&gt;https://blog.ston.fi/cross-chain-portfolio-rebalancing-how-htlc-and-rfq-swaps-work-for-defi-users/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TON to Base, BNB Chain, and Polygon: how cross-chain crypto swaps actually work&lt;/strong&gt; — STON.fi Blog, on bridge vs. resolver-based HTLC routing: &lt;a href="https://blog.ston.fi/ton-to-base-bnb-chain-and-polygon-how-cross-chain-crypto-swaps-actually-work/" rel="noopener noreferrer"&gt;https://blog.ston.fi/ton-to-base-bnb-chain-and-polygon-how-cross-chain-crypto-swaps-actually-work/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>The DeFi API for AI Agents: What Would It Need to Look Like?</title>
      <dc:creator>Web3KD</dc:creator>
      <pubDate>Mon, 24 Aug 2026 19:48:00 +0000</pubDate>
      <link>https://dev.to/web3kd/the-defi-api-for-ai-agents-what-would-it-need-to-look-like-2kmc</link>
      <guid>https://dev.to/web3kd/the-defi-api-for-ai-agents-what-would-it-need-to-look-like-2kmc</guid>
      <description>&lt;h1&gt;
  
  
  The DeFi API for AI Agents: What Would It Need to Look Like?
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Humans read a quote, glance at slippage, and click confirm. An AI agent can't glance at anything — it needs the entire decision made of structured data it can parse, verify, and act on without a screen in between. STON.fi's Omniston protocol already looks a lot like a first draft of that answer.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every DeFi interface built so far has one implicit assumption baked into it: a human is looking at the screen. The price is formatted for eyes. The slippage warning is a sentence, not a machine-checkable field. The "confirm" button assumes a person weighing a decision, not a program executing a policy. That assumption breaks the moment you try to hand a wallet to an autonomous agent instead of a person — and 2026 is the year that stopped being theoretical for a lot of builders on TON.&lt;/p&gt;

&lt;p&gt;So what would a DeFi API actually need to look like if the primary caller was an AI agent instead of a human? Not "AI-themed marketing copy on a swap button" — an actual protocol-level answer. It turns out Omniston, STON.fi's liquidity aggregation and cross-chain execution layer, already satisfies more of that answer than most people give it credit for, largely because it was built API-first rather than UI-first from day one.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💭 My honest take: most "AI + DeFi" content skips straight to autonomous trading bots without asking the more boring, more important question first — what does the &lt;em&gt;interface itself&lt;/em&gt; need to guarantee before any agent can safely act on it at all? That's the question this piece is actually about.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  ✅ Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Agents don't need prettier UIs — they need structured, parseable data at every step: quotes, execution status, errors, and refund conditions.&lt;/li&gt;
&lt;li&gt;A quote an agent can act on needs a stable identifier, an expiration, and machine-readable terms — not just a displayed number.&lt;/li&gt;
&lt;li&gt;Execution status needs to be observable programmatically, not inferred from a wallet notification a human would read.&lt;/li&gt;
&lt;li&gt;Omniston's SDK already exposes most of this surface — &lt;code&gt;requestForQuote&lt;/code&gt;, &lt;code&gt;buildTransfer&lt;/code&gt;, and &lt;code&gt;swapTrack&lt;/code&gt; map almost directly onto what an agent loop needs.&lt;/li&gt;
&lt;li&gt;The gap that remains isn't the API surface — it's standardized, cross-protocol schemas and scoped signing permissions that don't hand an agent your entire wallet.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧩 Why "Just Use the Existing API" Isn't Quite Enough
&lt;/h2&gt;

&lt;p&gt;A REST or WebSocket API that returns JSON already looks "machine-readable" on the surface. But an interface built for humans-via-frontend and one built for agents-via-API diverge in a few specific ways that matter a lot once nobody's eyes are actually on the screen:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Quotes need to be addressable, not just displayable.&lt;/strong&gt; A human reads "≈ 4.82 USDT" and moves on. An agent needs a stable &lt;code&gt;quoteId&lt;/code&gt; it can reference in a follow-up call, check for expiry, and use to build the exact transaction that quote described — not a re-derived approximation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State needs to be pollable or streamable, not inferred.&lt;/strong&gt; A human notices their wallet app changed. An agent needs to explicitly subscribe to or query execution status and get back a typed result: pending, settled, refundable, failed — not guess from a UI element that doesn't exist for it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Errors need to be classifiable, not just readable.&lt;/strong&gt; "Transaction failed" is fine for a person to read and shrug at. An agent needs to know &lt;em&gt;why&lt;/em&gt; — insufficient gas, stale quote, slippage exceeded — so it can decide whether to retry, adjust parameters, or halt entirely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Settlement guarantees need to be provable, not assumed.&lt;/strong&gt; An agent moving funds autonomously needs the underlying execution model to guarantee — cryptographically, not just contractually — that a failed trade doesn't silently strand funds.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  🔌 Omniston as a Working Example, Not a Hypothetical
&lt;/h2&gt;

&lt;p&gt;This is where it stops being theoretical. Omniston's Node.js SDK is built around exactly this shape: request a quote, get a typed, addressable object back, build a transaction from it, and track settlement as an observable stream — all without a UI in the loop at any point.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1 — Requesting a Quote
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Omniston&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@ston-fi/omniston-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;omniston&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Omniston&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wss://omni-ws.ston.fi&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;quoteStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;omniston&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestForQuote&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;settlementParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="cm"&gt;/* which settlement methods to allow */&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="na"&gt;offerAsset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="cm"&gt;/* asset the agent is offering */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;askAsset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="cm"&gt;/* asset the agent wants */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="cm"&gt;/* offer amount */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;quoteStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;quoteEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// quoteEvent carries a stable quoteId, expiry, and settlement terms —&lt;/span&gt;
  &lt;span class="c1"&gt;// an agent can log, validate, or discard this without ever rendering it&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what's already true here that a UI-first API wouldn't guarantee: the quote arrives as a typed event on a stream, not a string formatted for display. An agent can apply its own policy — "only accept quotes with price impact under X%" — entirely in code, before anything ever gets signed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 — Building a Transaction From That Exact Quote
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;omniston&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buildTransfer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// the exact quote object received above — not a re-derived price&lt;/span&gt;
  &lt;span class="na"&gt;sourceAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;blockchain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Blockchain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TON&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;agentWalletAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;destinationAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;blockchain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Blockchain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TON&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;agentWalletAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;gasExcessAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;blockchain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Blockchain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TON&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;agentWalletAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ton&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="c1"&gt;// messages is ready to sign and send — no re-interpretation needed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the addressability property from earlier, made concrete: the transaction is built from the &lt;em&gt;exact&lt;/em&gt; quote object, not a fresh price lookup that could have drifted. For a human, that drift shows up as slippage tolerance. For an agent, it needs to be closer to a contract: this quote, or nothing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 — Tracking Settlement as a Stream, Not a Guess
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;swapTrackStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;omniston&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;swapTrack&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;quoteId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quoteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;traderAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;outgoingTxQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// tx hash, message hash, or outgoing message body&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;swapTrackStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;$case&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pending&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;// agent can log and wait&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;settled&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;// agent can safely mark the position as complete&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;refunded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;// agent knows funds returned — no manual investigation needed&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is arguably the most important piece for agent-native design: settlement status is a subscribable stream with typed cases, not a wallet push notification meant for a person to read. An agent loop can &lt;code&gt;await&lt;/code&gt; a terminal state and branch its own logic on exactly what happened — no screen-scraping, no polling a UI, no assuming success because nothing obviously broke.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧠 Mapping This Back to the Four Requirements
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;What a human-first API gives you&lt;/th&gt;
&lt;th&gt;What Omniston's SDK actually gives an agent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Addressable quotes&lt;/td&gt;
&lt;td&gt;A displayed rate&lt;/td&gt;
&lt;td&gt;A &lt;code&gt;quoteId&lt;/code&gt;-bound, typed quote object&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observable state&lt;/td&gt;
&lt;td&gt;A UI notification&lt;/td&gt;
&lt;td&gt;A subscribable &lt;code&gt;swapTrack&lt;/code&gt; stream with typed cases&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Classifiable errors&lt;/td&gt;
&lt;td&gt;A toast message&lt;/td&gt;
&lt;td&gt;Typed event cases an agent can branch logic on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Provable settlement&lt;/td&gt;
&lt;td&gt;"Trust the app"&lt;/td&gt;
&lt;td&gt;HTLC-based atomic settlement — refund or settle, never stranded&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last row matters more for agents than for humans, arguably. A person who gets a confusing error can pause, screenshot it, ask someone. An agent executing autonomously needs the underlying settlement model itself to guarantee it can't end up in a state with no defined outcome — which is exactly what atomic, HTLC-based settlement is built to provide regardless of who or what initiated the trade.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚧 What's Still Genuinely Missing
&lt;/h2&gt;

&lt;p&gt;None of this means the problem is solved. A few gaps are real, and worth naming honestly rather than glossing over:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No standardized cross-protocol quote schema yet.&lt;/strong&gt; Omniston's typed quote object works beautifully &lt;em&gt;within&lt;/em&gt; Omniston. An agent comparing quotes across multiple unrelated DeFi protocols still has to normalize different shapes by hand — there's no shared "agent quote format" the way there's an emerging shared format for, say, tool-calling schemas in LLM APIs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signing permissions are still mostly all-or-nothing.&lt;/strong&gt; Handing an agent a wallet today generally means handing it broad signing capability, not a scoped permission like "up to $200, only this pair, only this week." Session-scoped, policy-bound signing is the piece that would let people actually trust autonomous agents with real capital, and it's not solved at the wallet layer yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resolver selection isn't yet exposed as agent-tunable criteria.&lt;/strong&gt; An agent might reasonably want to weight resolver reputation or historical fill reliability into its own decision-making, not just accept whichever quote wins on price. That's a reasonable next layer on top of what already exists.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🛠 A Minimal Sketch of an Agent-Friendly Wrapper
&lt;/h2&gt;

&lt;p&gt;Putting the pieces above together, here's a conceptual sketch — illustrative pseudocode, not a drop-in production snippet — of what an agent's decision loop could look like sitting on top of the primitives above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;agentSwapDecision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AgentPolicy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getBestQuote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offerAsset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;askAsset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;priceImpact&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxPriceImpact&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reject&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;price_impact_exceeded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expiresInMs&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;minQuoteLifetimeMs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reject&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;quote_too_stale&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;buildTransferFromQuote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;settlement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;executeAndTrack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quoteId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;settlement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$case&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;settled&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;complete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;quoteId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quoteId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;escalate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;settlement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$case&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The point of this sketch isn't the code itself — it's that every branch is a policy decision made &lt;em&gt;before&lt;/em&gt; signing, on typed data, with a defined fallback. That's the actual shape of "a DeFi API built for agents": not a smarter chatbot bolted onto a swap button, but a protocol where every step an agent needs to reason about is already a typed, addressable, observable object instead of something meant to be read off a screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧭 Conclusion
&lt;/h2&gt;

&lt;p&gt;The honest answer to "what would a DeFi API for AI agents need to look like" turns out to be less exotic than the framing suggests. It needs addressable quotes, observable execution, classifiable errors, and settlement guarantees that don't depend on anyone reading anything. Omniston's SDK already builds most of that surface for a reason that has nothing to do with AI hype — it was designed to be integration-first, so any program, human-facing or not, could reason about a swap the same way. The open work left isn't reinventing that foundation; it's standardizing it across protocols and giving agents narrower, safer permissions than "hold the whole wallet."&lt;/p&gt;

&lt;h2&gt;
  
  
  ❓ Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Can an AI agent already use Omniston to execute swaps autonomously today?&lt;/strong&gt;&lt;br&gt;
Technically, yes — the SDK exposes quote requests, transaction building, and settlement tracking as programmatic, typed calls that don't require a UI. Whether it's advisable depends entirely on the wallet permissions and safeguards wrapped around the agent, which is a separate, still-unsolved layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does using a typed API like Omniston's remove the need for slippage protection?&lt;/strong&gt;&lt;br&gt;
No. Price impact and slippage are still real; a typed API just lets an agent check those values programmatically and reject a quote automatically instead of a human eyeballing a percentage before confirming.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's the single biggest missing piece for safe agent-driven DeFi?&lt;/strong&gt;&lt;br&gt;
Scoped, policy-bound signing permissions. Most wallet integrations today still hand an agent broad signing capability rather than a narrow, revocable permission — that gap matters more than any missing API feature.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does atomic, HTLC-based settlement matter specifically for agents rather than humans?&lt;/strong&gt;&lt;br&gt;
Because an agent can't "notice something looks off" and pause the way a person might. The settlement model itself needs to guarantee a defined outcome — settled or refunded, never stranded — so an autonomous process never ends up in an undefined state it has to resolve manually.&lt;/p&gt;




&lt;h2&gt;
  
  
  📚 Sources and Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Omniston SDK — Node.js&lt;/strong&gt; — Full flow for quote requests, transaction building, and settlement tracking: &lt;a href="https://docs.ston.fi/developer-section/omniston/omniston-nodejs" rel="noopener noreferrer"&gt;https://docs.ston.fi/developer-section/omniston/omniston-nodejs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Omniston Protocol Overview&lt;/strong&gt; — Architecture and settlement modes, including cross-chain HTLC flows: &lt;a href="https://docs.ston.fi/developer-section/omniston" rel="noopener noreferrer"&gt;https://docs.ston.fi/developer-section/omniston&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Omniston SDK Reference&lt;/strong&gt; — Package overview and installation for TypeScript/JavaScript integration: &lt;a href="https://docs.ston.fi/developer-section/omniston/sdk" rel="noopener noreferrer"&gt;https://docs.ston.fi/developer-section/omniston/sdk&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;STON.fi Developer Introduction&lt;/strong&gt; — Full index of SDKs, REST API, and integration guides: &lt;a href="https://docs.ston.fi/" rel="noopener noreferrer"&gt;https://docs.ston.fi/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Indexing Blockchain Data: Why DeFi Apps Need More Than RPC Calls</title>
      <dc:creator>Web3KD</dc:creator>
      <pubDate>Sun, 23 Aug 2026 13:14:38 +0000</pubDate>
      <link>https://dev.to/web3kd/indexing-blockchain-data-why-defi-apps-need-more-than-rpc-calls-c0c</link>
      <guid>https://dev.to/web3kd/indexing-blockchain-data-why-defi-apps-need-more-than-rpc-calls-c0c</guid>
      <description>&lt;h1&gt;
  
  
  &lt;strong&gt;Indexing Blockchain Data: Why DeFi Apps Need More Than RPC Calls&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Ask a new blockchain developer how their app will "read the chain," and the answer is almost always the same: call the RPC. It's the obvious starting point, and for a narrow class of questions — what's this wallet's current balance, has this specific transaction confirmed — it's genuinely sufficient. The moment a DeFi app needs to answer a slightly more interesting question — every trade this pool has ever seen, every wallet that's ever farmed this token, how liquidity depth has shifted over the past month — RPC calls alone stop being a viable architecture, and most teams discover this the expensive way, in production, under load.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9upzi94t4hfs1jmr13tx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9upzi94t4hfs1jmr13tx.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This isn't a niche concern reserved for large-scale analytics dashboards. Even a modest DeFi frontend showing a pool's recent trade history, a user's farming rewards over time, or a simple "top pools by volume" leaderboard is already asking the kind of question raw RPC access wasn't designed to answer efficiently. The gap between "my app connects to the blockchain" and "my app can actually show users the data they expect" is almost entirely made of this distinction, and it's worth understanding precisely rather than discovering by accident when an endpoint starts timing out under real traffic.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;🗨️&lt;/em&gt; "RPCs provide connectivity, indexers make data usable, oracles connect applications to the real world and other chains, Layer-2s make execution affordable, and storage keeps assets and history available." &lt;em&gt;— Web3 Infrastructure Stack overview, 2026&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔&lt;/p&gt;

&lt;h3&gt;
  
  
  🗄️ &lt;strong&gt;&lt;em&gt;What an RPC Node Is Actually Built to Answer&lt;/em&gt;&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb3m77i92e0s7ztnjgiu3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb3m77i92e0s7ztnjgiu3.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An RPC endpoint is a gateway to a single node's current or near-current state — it excels at point-in-time, single-entity questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What's this address's balance right now&lt;/li&gt;
&lt;li&gt;Has this transaction confirmed&lt;/li&gt;
&lt;li&gt;What does calling this contract method return, given current state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What it's structurally weak at is aggregation across time or across many entities at once — "every swap in this pool over the last 30 days" isn't a single state read, it's a query across a history the node wasn't built to summarize on demand. Asking an RPC node to answer that means either replaying enormous ranges of blocks per request, or accepting that the app simply can't answer that question efficiently at all.&lt;/p&gt;

&lt;p&gt;This limitation isn't a bug in any particular provider's implementation — it's a direct consequence of what a node is actually built to do. A node maintains current state and validates new blocks; it was never meant to be a general-purpose historical database, and asking it to behave like one means fighting the grain of its own design. Industry infrastructure guides increasingly frame this plainly: request volume that would be trivial for a purpose-built index can be enough to "overwhelm shared-tier endpoints" when routed through raw RPC methods instead, and even dedicated, paid RPC tiers hit real limits once a query pattern crosses from "look up one thing" into "reconstruct history across many things."&lt;/p&gt;

&lt;p&gt;▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Where This Breaks in Practice: TON's Own Architecture as the Clearest Example&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;TON is a genuinely useful case study here, because its own design makes the RPC-only limitation unusually visible rather than a subtle inefficiency.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;🗨️&lt;/em&gt; "The TON DeFi ecosystem — including DEXes like STON.fi, lending protocols, and Jetton issuance — requires both real-time method access (v2) for transaction submission and indexer access (v3) for token balance lookups, liquidity pool reads, and historical trade data. A provider that only surfaces v2 forces teams to run their own indexer or accept incomplete data coverage." &lt;em&gt;— Chainstack, TON RPC Provider Guide, 2026&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff8jjpshzxvhh4zxsqbz1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff8jjpshzxvhh4zxsqbz1.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a direct, named acknowledgment that a DEX like STON.fi cannot function on RPC access alone. TON's asynchronous, sharded architecture — messages between contracts don't resolve in the same block the way a single-chain synchronous call does — makes naive "just poll the RPC repeatedly" patterns even less viable than on more traditional chains. Heavy read patterns specific to this ecosystem — enumerating jetton masters, tracing transactions by message, reconstructing a pool's trade history — are explicitly called out as workloads that "overwhelm shared-tier endpoints quickly." This isn't a hypothetical scaling concern; it's a documented, current-year operational reality for exactly the kind of app STON.fi is.&lt;/p&gt;

&lt;p&gt;▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔&lt;/p&gt;

&lt;h3&gt;
  
  
  🔍 &lt;strong&gt;What an Indexer Actually Does Differently&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdpkgvhqcgat3tfplwnuv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdpkgvhqcgat3tfplwnuv.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An indexer isn't a faster RPC node — it's a fundamentally different data shape sitting behind the chain.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It watches the chain continuously and writes relevant events into a queryable database as they happen, rather than waiting to be asked&lt;/li&gt;
&lt;li&gt;It pre-aggregates the kind of historical, cross-entity questions an RPC node can't answer efficiently — total volume by pool, every position a wallet has ever held, time-series depth over a date range&lt;/li&gt;
&lt;li&gt;It exposes that data through a query interface — commonly GraphQL or SQL — built for exactly the aggregation patterns a raw JSON-RPC method was never designed to serve&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The distinction matters architecturally: an RPC node answers "what is true right now, about this one thing." An indexer answers "what has been true, across everything, over time" — and a DeFi frontend genuinely needs both, for different parts of the same interface.&lt;/p&gt;

&lt;p&gt;▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚡ &lt;strong&gt;Where STON.fi's Own Architecture Already Reflects This Split&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This isn't just an abstract infrastructure argument — it shows up directly in how STON.fi structures its own developer-facing API. STON.fi's REST API exposes pool data, jetton information, protocol statistics, and fee data specifically so integrating developers never have to run their own indexing pipeline just to answer "what does this pool's history look like" — the aggregation work has already been done, once, centrally, rather than pushed onto every team building on top of the protocol.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Without an indexing layer, "get this pool's 30-day volume" means:&lt;/span&gt;
&lt;span class="c1"&gt;// manually paginating through raw transaction history yourself&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getVolumeTheHardWay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;poolAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;allTxs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;batch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rpcClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTransactions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;poolAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;allTxs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;allTxs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextCursor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;allTxs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;ENOUGH_TO_COVER_30_DAYS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;aggregateVolumeFromRawTxs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;allTxs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// now do this correctly, yourself&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// With STON.fi's own indexed API, the same question is one call&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getVolumeTheRealWay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;poolAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;stonfiApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPoolStats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;poolAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;period&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;30d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;volume&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// already aggregated server-side&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second function isn't just shorter code — it's the entire architectural point of this article expressed as two functions doing the same job at radically different costs, in both engineering time and runtime load on shared infrastructure.&lt;/p&gt;

&lt;p&gt;▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔&lt;/p&gt;

&lt;h3&gt;
  
  
  🛰️ &lt;strong&gt;The Broader Infrastructure Landscape Solving This Same Problem&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faoy6cspgv4mvwxkbd7y4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faoy6cspgv4mvwxkbd7y4.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;STON.fi's own API is one answer to this problem, specific to its own protocol data. The wider ecosystem has converged on a few recognizable patterns for the general case:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Subgraph-style indexing.&lt;/strong&gt; A widely adopted pattern across DeFi and NFT protocols generally: define what on-chain events matter, and a hosted indexing layer continuously ingests and exposes them through a standard query interface — the developer writes what to track, not how to scan the chain for it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Streaming pipelines into owned infrastructure.&lt;/strong&gt; Rather than querying a third party's hosted index at request time, some platforms stream relevant on-chain events directly into a team's own database in near real time — trading some setup complexity for full ownership of the resulting data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;High-throughput retrieval layers purpose-built for historical backfills.&lt;/strong&gt; Distinct from live indexing, this category focuses specifically on event-heavy, high-volume historical queries — the kind of workload that would otherwise mean manually replaying enormous block ranges through standard RPC calls, compressed instead into a retrieval layer built for exactly that access pattern.&lt;/p&gt;

&lt;p&gt;Each of these exists because the same underlying gap keeps appearing across every chain and every serious DeFi application: raw node access answers current-state questions well and historical, aggregate questions poorly, and that gap doesn't close by adding more RPC capacity — it closes by adding a genuinely different kind of infrastructure layer. Teams that try to close it by simply paying for a bigger or faster RPC plan tend to discover that the ceiling moves, but doesn't disappear — the query pattern was always the mismatch, not the provider's raw throughput.&lt;/p&gt;

&lt;p&gt;▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚖️ &lt;strong&gt;Comparing on the Dimensions That Actually Matter&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzx8u9hfy0rj7k82b2o7b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzx8u9hfy0rj7k82b2o7b.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;💧 Point-in-time reads vs. historical aggregation.&lt;/strong&gt; An RPC call answers "what is the state right now." An index answers "what has the state been, summarized, over a range" — and a real DeFi frontend needs both simultaneously, often on the same screen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🧭 Single-entity queries vs. cross-entity queries.&lt;/strong&gt; Checking one wallet's balance is RPC-shaped. Ranking every liquidity provider in a pool by size is index-shaped. Trying to force the second pattern through the first tool is where naive architectures fail under real load.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⏱️ Request-time computation vs. pre-aggregated results.&lt;/strong&gt; Computing 30-day volume by replaying transactions on every page load is request-time work that scales badly with traffic. An indexed API returning a pre-computed figure scales with how often the underlying data actually changes, not with how many users ask about it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⚔️ Running your own indexing stack vs. relying on a protocol's own indexed API.&lt;/strong&gt; Self-hosting an indexing pipeline offers full control but comes with real, ongoing operational cost. Using a protocol's own indexed endpoints — where they exist, as with STON.fi's own REST API — removes that operational burden entirely for exactly the queries that protocol already needed to answer for its own frontend.&lt;/p&gt;

&lt;p&gt;▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔&lt;/p&gt;

&lt;h2&gt;
  
  
  ✅ What Genuinely Justifies Adding an Indexing Layer
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Any question that spans time or many entities at once.&lt;/strong&gt; Historical volume, wallet-level position history, and pool-ranking queries are the clearest signal that plain RPC access has been outgrown.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request patterns that would otherwise mean repeated, expensive chain scans.&lt;/strong&gt; If serving a single page requires replaying a meaningful range of transaction history on every load, that computation belongs in a pre-aggregated index, not in the request path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read patterns explicitly documented as overwhelming shared infrastructure.&lt;/strong&gt; When a chain's own tooling ecosystem calls out specific query patterns as needing dedicated indexer access rather than standard RPC methods, that's a direct, reliable signal for architecture decisions, not a guess.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  ⚠️ What's Worth Understanding Correctly
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;An indexer is not a faster RPC node.&lt;/strong&gt; It's a structurally different data layer, built for aggregation and history rather than point-in-time state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Using a protocol's own indexed API isn't a shortcut — it's often the correct architecture.&lt;/strong&gt; When STON.fi already exposes pool and fee data through its own API, rebuilding that aggregation independently duplicates work the protocol has already done well.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TON's async, sharded design makes this distinction more visible than on some other chains, not less relevant.&lt;/strong&gt; The v2/v3 RPC split documented by TON infrastructure providers exists specifically because this chain's architecture makes naive polling patterns fail faster and more visibly than on a simpler synchronous chain.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🏁 Bottom Line
&lt;/h2&gt;

&lt;p&gt;RPC calls answer the question DeFi apps ask least interestingly — "what's true right now, about this one thing" — and answer it well. The moment an app needs to answer anything that spans history or many entities at once, which is most of what a real trading interface actually shows a user, RPC access alone stops scaling, and the documented reality on TON specifically — where DEXes like STON.fi are explicitly named as needing indexer-grade access alongside standard RPC methods — makes that gap unusually concrete rather than theoretical. Building on top of a protocol's own indexed API where one exists, rather than reimplementing that aggregation independently, is usually the correct answer, not a compromise.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔗 Sources &amp;amp; Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Chainstack — "Top 6 TON RPC Providers for Mini Apps and Production in 2026" (STON.fi-specific v2/v3 requirements) — &lt;a href="https://chainstack.com/top-6-ton-rpc-providers-for-telegram-mini-apps-and-production-workloads-in-2026/" rel="noopener noreferrer"&gt;https://chainstack.com/top-6-ton-rpc-providers-for-telegram-mini-apps-and-production-workloads-in-2026/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Blockchain Council — "Web3 Infrastructure Stack 2026: What You Need" — &lt;a href="https://www.blockchain-council.org/web-3/web3-infrastructure-stack-2026-rpcs-indexers-oracles-layer-2s-storage/" rel="noopener noreferrer"&gt;https://www.blockchain-council.org/web-3/web3-infrastructure-stack-2026-rpcs-indexers-oracles-layer-2s-storage/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Ormi Labs — "What Is Blockchain Indexing? Types, Limitations and Tools" — &lt;a href="https://blog.ormilabs.com/what-is-blockchain-indexing-part1/" rel="noopener noreferrer"&gt;https://blog.ormilabs.com/what-is-blockchain-indexing-part1/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OnFinality — "The 2026 Guide to Blockchain Infrastructure Providers" — &lt;a href="https://blog.onfinality.io/2026-guide-to-blockchain-infrastructure/" rel="noopener noreferrer"&gt;https://blog.onfinality.io/2026-guide-to-blockchain-infrastructure/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;STON.fi — Developer Documentation Introduction — &lt;a href="https://docs.ston.fi/" rel="noopener noreferrer"&gt;https://docs.ston.fi/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;This article reflects independent research based on publicly available infrastructure documentation and STON.fi's public developer documentation as of mid-2026. Provider pricing, API coverage, and TON's own RPC architecture evolve over time — always verify current details directly on the relevant provider's documentation before making infrastructure decisions.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
