<?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>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>
    <item>
      <title>Transaction Lifecycle: What Actually Happens After You Click Swap</title>
      <dc:creator>Web3KD</dc:creator>
      <pubDate>Sun, 23 Aug 2026 12:26:15 +0000</pubDate>
      <link>https://dev.to/web3kd/transaction-lifecycle-what-actually-happens-after-you-click-swap-aeg</link>
      <guid>https://dev.to/web3kd/transaction-lifecycle-what-actually-happens-after-you-click-swap-aeg</guid>
      <description>&lt;h1&gt;
  
  
  &lt;strong&gt;Transaction Lifecycle: What Actually Happens After You Click Swap&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;A swap transaction isn't one event — it's a state machine, moving through distinct, named phases between the moment a user taps "confirm" and the moment funds actually settle. This piece traces that lifecycle stage by stage on STON.fi, with the actual code shape at each transition, because this is genuinely an engineering topic underneath the trading language, and it deserves to be read as one.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;🗨️&lt;/em&gt; "Simulate the swap to obtain routing metadata (expected amounts, vault info, and the full router object). Feed simulationResult.router directly into dexFactory() to build contracts dynamically." &lt;em&gt;— STON.fi, SDK v2 Swap Documentation&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;h3&gt;
  
  
  🎞️ &lt;strong&gt;&lt;em&gt;The Six States a Transaction Actually Passes Through&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%2Fd9rla925ucclc1okv4hu.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%2Fd9rla925ucclc1okv4hu.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before the code, the shape of the lifecycle itself:&lt;/p&gt;

&lt;p&gt;→ &lt;strong&gt;Simulated&lt;/strong&gt; — a live calculation against current reserves, no commitment yet&lt;br&gt;
→ &lt;strong&gt;Parameterized&lt;/strong&gt; — the simulation's output converted into concrete transaction fields&lt;br&gt;
→ &lt;strong&gt;Handed off&lt;/strong&gt; — passed to the wallet layer for review&lt;br&gt;
→ &lt;strong&gt;Signed&lt;/strong&gt; — the trader's actual commitment point&lt;br&gt;
→ &lt;strong&gt;Broadcast&lt;/strong&gt; — submitted to the network with a built-in expiration&lt;br&gt;
→ &lt;strong&gt;Settled&lt;/strong&gt; — atomically completed, or reverted, with no state in between&lt;/p&gt;

&lt;p&gt;Every one of these is a distinct, inspectable moment. Treating "confirm swap" as one atomic user action hides that six separate things have to happen correctly, in order, for that action to actually succeed.&lt;/p&gt;

&lt;p&gt;▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;State One: Simulated&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Nothing here is final, and that's the entire point of this stage existing separately from what follows.&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;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="na"&gt;offerAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;offerJetton&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;askJetton&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="s1"&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="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// simulationResult now carries: expected output, fee breakdown, and&lt;/span&gt;
&lt;span class="c1"&gt;// simulationResult.router — the current router object, not a saved address&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This step can run any number of times, freely, with zero cost — which is precisely why it's a poor idea to cache and reuse an old simulation result rather than re-running it right before the next stage.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  ⚙️ &lt;strong&gt;State Two: Parameterized&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The simulation's output gets converted into the actual fields a transaction needs — this is where "approximately what you'll get" becomes "exactly what will be enforced on-chain."&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;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;span class="c1"&gt;// never hardcoded&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;txParams&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;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSwapJettonToJettonTxParams&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;userWalletAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;offerJettonAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;offerJetton&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;askJettonAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;askJetton&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;offerAmount&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;minAskAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;calculateMinAskAmount&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;askUnits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slippageBps&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;queryId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;generateQueryId&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 &lt;code&gt;minAskAmount&lt;/code&gt; field is the crux of this state: it's the moment a slippage percentage typed into a UI becomes a specific integer that a smart contract will actually check against.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  🖊️ &lt;strong&gt;State Three: Handed Off, Then Signed&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;These are technically two states, but they happen across a single user-facing moment — the wallet prompt — so it's worth treating them together.&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;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="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;txParams&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="nf"&gt;toString&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;txParams&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="nf"&gt;toString&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;txParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBoc&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything before this call was recalculable and free. &lt;code&gt;sendTransaction&lt;/code&gt; is where that stops being true — the user's wallet signature is the actual, irreversible commitment point in the entire lifecycle. Before it, the transaction is a proposal. After it, it's a request the network will actually act on.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  📤 &lt;strong&gt;State Four: Broadcast&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Once signed, the payload — a base64-encoded BOC, TON's native cell serialization format — actually leaves the client and enters the network. The &lt;code&gt;validUntil&lt;/code&gt; field set in the previous state now becomes active protection:&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;// If the network hasn't processed this by validUntil, it's discarded —&lt;/span&gt;
&lt;span class="c1"&gt;// never executed against reserves that may no longer reflect this quote&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is also the point where, for a same-chain swap, TON's transaction model takes over: the transaction will either execute completely or fail completely. There's no documented partial-execution state at this layer — a multi-chunk route either lands as calculated, chunk by chunk, or the whole thing reverts.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  🔁 &lt;strong&gt;State Five: Settlement — and Where Cross-Chain Genuinely Diverges&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;For a same-chain trade, settlement is close to instantaneous once the transaction is included in a block. For a cross-chain trade, this state gets meaningfully more complex, because TON's own atomicity guarantee doesn't extend to a second, separate blockchain automatically.&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;// Cross-chain settlement adds an HTLC layer on top of the same lifecycle:&lt;/span&gt;
&lt;span class="c1"&gt;// - funds lock behind a shared hash + time condition on both chains&lt;/span&gt;
&lt;span class="c1"&gt;// - completion reveals the secret and releases both sides&lt;/span&gt;
&lt;span class="c1"&gt;// - if either side fails to complete in time, the time lock&lt;/span&gt;
&lt;span class="c1"&gt;//   auto-refunds the locked side rather than leaving it stuck&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is why a cross-chain swap through STON.fi's Omniston takes measurably longer than a same-chain one — it's not inefficiency, it's a deliberate trade of speed for a correctness guarantee that two independent blockchains don't provide each other on their own.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  🎞️ &lt;strong&gt;Watching the State Machine From the Outside&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;A well-built integration doesn't just fire a transaction and hope — it subscribes to the lifecycle's own status stream rather than polling a balance and guessing.&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;event&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;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;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;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;filled&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nf"&gt;confirmSuccess&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="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="s1"&gt;partiallyFilled&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;// Reflects the quote-matching stage, not a broken on-chain transaction —&lt;/span&gt;
      &lt;span class="c1"&gt;// some connected liquidity didn't fully match before execution&lt;/span&gt;
      &lt;span class="nf"&gt;handlePartialFill&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="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="s1"&gt;aborted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nf"&gt;showRetryOption&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Treating &lt;code&gt;partiallyFilled&lt;/code&gt; as equivalent to a crashed transaction is a common misread — it describes what happened during quote-matching among resolvers and pools, before final on-chain execution, not a submitted transaction that stopped halfway.&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%2F1nuu5oxb531r73bb7p1w.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%2F1nuu5oxb531r73bb7p1w.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;💧 Free states vs. committed states.&lt;/strong&gt; Everything through "parameterized" costs nothing and binds nobody. Everything from "signed" onward is real. Conflating these two categories is the most common source of confusion about what a "failed" swap actually means at each point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🧭 Same-chain settlement vs. cross-chain settlement.&lt;/strong&gt; One relies on native blockchain atomicity and settles quickly. The other layers HTLC-based correctness on top, deliberately trading speed for a guarantee TON alone can't provide across a chain boundary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⏱️ Polling vs. subscribing.&lt;/strong&gt; Manually checking a wallet balance after broadcasting is a weak signal — the trade could be pending, reverted, or still routing. Subscribing to &lt;code&gt;trackTrade&lt;/code&gt; gets an explicit state instead of an inference.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  ✅ What Genuinely Makes This Lifecycle Safe to Build On
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Every state before signature is inspectable and repeatable.&lt;/strong&gt; A developer — or a curious user — can trigger and re-trigger simulation and parameter-building without any real-world consequence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The transaction's own fields, not external assumptions, enforce the guarantees.&lt;/strong&gt; &lt;code&gt;minAskAmount&lt;/code&gt; and &lt;code&gt;validUntil&lt;/code&gt; are baked into the payload itself, not just displayed in a UI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Status is streamed, not inferred.&lt;/strong&gt; &lt;code&gt;trackTrade&lt;/code&gt; reports real states instead of forcing an integration to guess from a balance check.&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;"Broadcast" and "settled" are not the same state.&lt;/strong&gt; A transaction can be genuinely on the network and still fail atomically at settlement if conditions shifted enough.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;partiallyFilled&lt;/code&gt; belongs to quote-matching, not execution.&lt;/strong&gt; Once a transaction actually broadcasts on TON, it's binary — complete or reverted — with this status reflecting an earlier stage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-chain lifecycle length is a designed tradeoff.&lt;/strong&gt; The extra time isn't latency to optimize away; it's the HTLC window that prevents funds from ever being stuck between two chains.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;"What happens after you click swap" is really six distinct, inspectable states — simulated, parameterized, handed off, signed, broadcast, and settled — each with its own guarantees and its own failure mode. Building against this lifecycle explicitly, rather than treating a swap as one opaque action, is what separates an integration that degrades gracefully under real-world conditions from one that just breaks silently the first time a pool's reserves shift at the wrong moment.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;STON.fi — SDK v2 Swap Documentation — &lt;a href="https://docs.ston.fi/developer-section/dex/sdk/v2/swap" rel="noopener noreferrer"&gt;https://docs.ston.fi/developer-section/dex/sdk/v2/swap&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;STON.fi — Transaction Sending via TON Connect — &lt;a href="https://docs.ston.fi/developer-section/common/transaction-sending/tonconnect" rel="noopener noreferrer"&gt;https://docs.ston.fi/developer-section/common/transaction-sending/tonconnect&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;STON.fi — Omniston gRPC Integration Guide (trackTrade, fill states) — &lt;a href="https://docs.ston.fi/developer-section/omniston/swap/grpc" rel="noopener noreferrer"&gt;https://docs.ston.fi/developer-section/omniston/swap/grpc&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;li&gt;STON.fi — Omniston Protocol Overview (HTLC cross-chain settlement) — &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;/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. Code examples are simplified for clarity and are not a verbatim copy of the official SDK. Transaction flow and protocol mechanics evolve as the system ships updates — always verify current functionality directly on docs.ston.fi before shipping a production integration.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The Last Mile: Taking a STON.fi Integration From Test Network to Real Users</title>
      <dc:creator>Web3KD</dc:creator>
      <pubDate>Sat, 22 Aug 2026 11:31:54 +0000</pubDate>
      <link>https://dev.to/web3kd/the-last-mile-taking-a-stonfi-integration-from-test-network-to-real-users-2ho0</link>
      <guid>https://dev.to/web3kd/the-last-mile-taking-a-stonfi-integration-from-test-network-to-real-users-2ho0</guid>
      <description>&lt;h1&gt;
  
  
  &lt;strong&gt;The Last Mile: Taking a STON.fi Integration From Test Network to Real Users&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Getting a swap working in a test environment is the easy 80% of shipping a STON.fi integration. The remaining 20% — the part that actually determines whether real users lose money to a preventable bug — is almost entirely about the gap between "works in sandbox" and "safe on mainnet." This piece walks through that gap concretely, with the specific patterns STON.fi's own documentation flags as production-critical.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;🗨️&lt;/em&gt; "The production-ready pattern is API-driven. Always let the STON.fi API dictate which router to use, and work against the mainnet endpoint. This keeps your integration compatible with future router upgrades and avoids hardcoding contract addresses." &lt;em&gt;— STON.fi, SDK v2 Swap Documentation&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;h3&gt;
  
  
  🧳 &lt;strong&gt;&lt;em&gt;Why "It Worked in Testing" Isn't the Bar&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%2Fwg5684jr96a0q6g9ddeh.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%2Fwg5684jr96a0q6g9ddeh.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Test environments are deliberately forgiving in ways production isn't. Three gaps close the distance between the two:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔲 &lt;strong&gt;Endpoints are genuinely separate, not a toggle.&lt;/strong&gt; &lt;code&gt;wss://omni-ws-sandbox.ston.fi&lt;/code&gt; and &lt;code&gt;wss://omni-ws.ston.fi&lt;/code&gt; are different environments entirely — a misconfigured environment variable is the single most avoidable production incident.&lt;/li&gt;
&lt;li&gt;🔳 &lt;strong&gt;Contract addresses differ, and mixing them is dangerous, not just broken.&lt;/strong&gt; STON.fi's own DEX core repository lists distinct mainnet (&lt;code&gt;EQB3ncy...&lt;/code&gt;) and testnet (&lt;code&gt;EQBsGx9...&lt;/code&gt;) router addresses, and third-party integration docs explicitly warn against sending funds across that boundary in either direction.&lt;/li&gt;
&lt;li&gt;▶️ &lt;strong&gt;Hardcoded values that "worked" in testing quietly rot in production.&lt;/strong&gt; A fee constant or router address that was correct on day one silently becomes wrong the moment STON.fi ships a router upgrade — which is precisely the failure mode the API-driven pattern exists to prevent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are exotic mistakes. They're the ordinary, boring gaps that separate a demo from a system real money moves through.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  &lt;strong&gt;The Single Highest-Leverage Rule: Let the API Decide, Don't Decide for It&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;If this article had to compress into one sentence, it would be this one, because STON.fi's own documentation states it as explicitly as production advice ever gets stated.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;🗨️&lt;/em&gt; "STON.fi's REST API (api.ston.fi) only serves mainnet data, so every production swap should follow this flow: simulate the swap to obtain routing metadata, feed simulationResult.router directly into dexFactory() to build contracts dynamically." &lt;em&gt;— STON.fi, SDK v2 Swap Documentation&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The instinct after testing successfully is to lock in what worked — save the router address you tested against, hardcode it, ship it. That instinct is exactly backwards for this ecosystem. STON.fi's router can be upgraded, and an integration that hardcodes a specific router address instead of asking the API which one to use will break silently the next time that happens — not with an error message, but with a transaction that's quietly built against a router that's no longer the current one.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  🚧 &lt;strong&gt;The Shipping Checklist, One Gap at a Time&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;🔲 Confirm every environment variable before the first sandbox test, not the last.&lt;/strong&gt; Both the WebSocket endpoint and the REST API base URL need to point at sandbox during testing and mainnet in production — a single missed variable in a deploy config is enough to send test transactions against real liquidity, or worse, production transactions against a test router.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔳 Never hardcode a router or contract address, even one that's "definitely stable."&lt;/strong&gt; Follow the documented pattern exactly: simulate first, then feed the simulation's own router object into &lt;code&gt;dexFactory()&lt;/code&gt; to build contracts dynamically. This single pattern absorbs future router upgrades automatically, without a code change on your side.&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 — breaks silently on the next router upgrade&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;EQB3ncyBUTjZUA5EnFKR5_EnOMI9V1tTEAAPaiU71gc4TiUt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ API-driven — always current, per STON.fi's own recommended pattern&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;swapParams&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;&lt;strong&gt;▶️ Calculate fees dynamically, never as a hardcoded constant.&lt;/strong&gt; STON.fi's own SDK constants for fees exist mainly as fallbacks — the documented best practice is calculating fees from current config parameters rather than relying on values that were correct when the code was written but aren't guaranteed to stay that way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;◀️ Prefer the SDK and TonConnect over manual BOC compilation, unless you have a genuinely specialized need.&lt;/strong&gt; STON.fi's own API reference is explicit that custom BOC compilation should be reserved for advanced use cases — the SDK handles edge cases and receives official support that a hand-rolled transaction builder won't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔲 Test both refund paths and the documented exit codes before launch, not after a user reports a problem.&lt;/strong&gt; This means deliberately exercising the failure modes covered in STON.fi's own error handling — a zero &lt;code&gt;minAskAmount&lt;/code&gt;, insufficient gas producing exit code 37, and both the default and explicitly-set &lt;code&gt;refundAddress&lt;/code&gt; behaviors — as part of the pre-launch test suite, not as reactive debugging once something has already gone wrong for a real user.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  🗝️ &lt;strong&gt;What Changes Once Real Referral Fees and Real Vaults Are Involved&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;If an integration routes through a referral program, there's a specific mechanic worth understanding before launch rather than after the first payout question comes in.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;🗨️&lt;/em&gt; "DEX v2 accumulates each swap's referral portion in a dedicated Vault contract (one per referrer × token pair). Fees can be configured in the 0.01%–1% range and must later be withdrawn by the referrer." &lt;em&gt;— STON.fi, v2 Smart Contract Documentation&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This means referral revenue doesn't arrive automatically in a wallet — it accrues in a per-referrer, per-pair Vault contract that requires an explicit withdrawal step. An integration's launch checklist should include verifying that this withdrawal flow actually works end to end in sandbox, not just that the referral fee is correctly attached to outgoing swap transactions. A referral fee that accrues correctly but can never actually be withdrawn is a production bug that looks like a success in every log line up until someone tries to claim it.&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;strong&gt;💧 Hardcoded stability vs. adaptive stability.&lt;/strong&gt; A hardcoded router address feels more stable during development because it never changes on you unexpectedly. In production, that's exactly backwards — the API-driven pattern is the version that actually stays stable as STON.fi's own infrastructure evolves underneath it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🧭 Sandbox correctness vs. production correctness.&lt;/strong&gt; Sandbox testing proves your logic is right for the inputs you thought to test. Production correctness additionally requires that your environment configuration, fee calculation, and referral withdrawal flow are all verified against real, current mainnet state — a genuinely broader bar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⏱️ Reactive debugging vs. pre-launch failure testing.&lt;/strong&gt; Discovering an exit-code handling gap from a confused user's support ticket costs far more — in trust and in engineering time — than deliberately triggering that same exit code in a sandbox test the week before launch.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  ✅ What a Genuinely Production-Ready Integration Gets Right
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Every router and contract address comes from a live simulation, never a saved constant.&lt;/strong&gt; This single pattern absorbs STON.fi's own infrastructure changes automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment separation is verified in CI, not assumed from a config file.&lt;/strong&gt; Sandbox and mainnet endpoints get explicitly checked before any test suite runs, closing off the most common and most avoidable production incident.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Referral and fee mechanics are tested end to end, including withdrawal.&lt;/strong&gt; Fees accruing correctly is only half the mechanism — confirming they can actually be claimed is the other half most teams skip.&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;Sandbox and mainnet contract addresses are not interchangeable, and mixing them is an active risk, not just an error.&lt;/strong&gt; Third-party integration guides explicitly warn against sending funds across that boundary in either direction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A router upgrade on STON.fi's side is not your integration's emergency if you followed the API-driven pattern.&lt;/strong&gt; It only becomes one if a router address was hardcoded somewhere it shouldn't have been.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual BOC compilation is a deliberate, advanced choice, not a shortcut.&lt;/strong&gt; STON.fi's own documentation reserves it for specialized cases precisely because the SDK already handles the edge cases a hand-rolled path would need to reimplement.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Shipping a STON.fi integration from sandbox to mainnet isn't about writing more code — it's about removing the specific hardcoded assumptions that work fine in a test environment and quietly fail in a production one: fixed router addresses instead of live simulation results, static fee constants instead of dynamic config reads, and untested refund or withdrawal paths that only get discovered once a real user is waiting on them. The gap between a working demo and a production-ready integration is almost entirely made of these small, documented, entirely avoidable decisions.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;STON.fi — SDK v2 Swap Documentation (API-driven pattern, dexFactory) — &lt;a href="https://docs.ston.fi/developer-section/dex/sdk/v2/swap" rel="noopener noreferrer"&gt;https://docs.ston.fi/developer-section/dex/sdk/v2/swap&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;STON.fi — v2 Smart Contract Documentation (referral Vaults, fee configuration) — &lt;a href="https://docs.ston.fi/developer-section/dex/smart-contracts/v2" rel="noopener noreferrer"&gt;https://docs.ston.fi/developer-section/dex/smart-contracts/v2&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;li&gt;GitHub — ston-fi/dex-core (mainnet/testnet addresses, bug bounty contact) — &lt;a href="https://github.com/ston-fi/dex-core" rel="noopener noreferrer"&gt;https://github.com/ston-fi/dex-core&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Tact Documentation — STON.fi Integration Cookbook (testnet/mainnet fund-mixing warning) — &lt;a href="https://docs.tact-lang.org/cookbook/dexes/stonfi/" rel="noopener noreferrer"&gt;https://docs.tact-lang.org/cookbook/dexes/stonfi/&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 and open-source repositories as of mid-2026. Contract addresses, fee ranges, and recommended patterns 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>webdev</category>
      <category>programming</category>
      <category>cryptocurrency</category>
    </item>
  </channel>
</rss>
