<?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: StarGate</title>
    <description>The latest articles on DEV Community by StarGate (@stargatezone).</description>
    <link>https://dev.to/stargatezone</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%2F4202714%2F91c356f3-fe98-4025-a521-e2cd44be1583.png</url>
      <title>DEV Community: StarGate</title>
      <link>https://dev.to/stargatezone</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/stargatezone"/>
    <language>en</language>
    <item>
      <title>Measuring a stablecoin depeg from two AMM pools: the Binance hack in 1-minute candles</title>
      <dc:creator>StarGate</dc:creator>
      <pubDate>Wed, 04 Nov 2026 14:28:23 +0000</pubDate>
      <link>https://dev.to/stargatezone/measuring-a-stablecoin-depeg-from-two-amm-pools-the-binance-hack-in-1-minute-candles-4kbk</link>
      <guid>https://dev.to/stargatezone/measuring-a-stablecoin-depeg-from-two-amm-pools-the-binance-hack-in-1-minute-candles-4kbk</guid>
      <description>&lt;p&gt;There is no USDT/USDC market in our dataset, yet on October 29, 2026 we can say USDT traded at 0.9054 USDC on Ethereum at 21:09 UTC. This post shows how: read the cross rate through a third asset, keep the candles honest, and know what the number does and does not mean. All numbers come from our own observer, which records Binance trades and every swap of 23 BTC and ETH markets in Ethereum pools (Uniswap v2/v3/v4, SushiSwap, Curve, Balancer, Fluid, DODO) into 1m…1d candles.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trick: a cross rate through ETH
&lt;/h2&gt;

&lt;p&gt;Two pools in the same protocol, same base asset, different stablecoins:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;P_usdc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Uniswap&lt;/span&gt; &lt;span class="n"&gt;v3&lt;/span&gt; &lt;span class="n"&gt;WETH&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;USDC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# USDC per ETH
&lt;/span&gt;&lt;span class="n"&gt;P_usdt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Uniswap&lt;/span&gt; &lt;span class="n"&gt;v3&lt;/span&gt; &lt;span class="n"&gt;WETH&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;USDT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# USDT per ETH
&lt;/span&gt;&lt;span class="n"&gt;implied&lt;/span&gt; &lt;span class="n"&gt;USDT&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;USDC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;P_usdc&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;P_usdt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If both pools are arbitraged against each other, the ratio is the market's price of USDT in USDC. Same idea for wrapped bitcoin: &lt;code&gt;close(WBTC/USDT pool) / close(Binance BTC/USDT)&lt;/code&gt; is WBTC in BTC, because both legs are quoted in USDT.&lt;/p&gt;

&lt;h2&gt;
  
  
  What makes the number trustworthy
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Same minute, both with trades.&lt;/strong&gt; We join candles on &lt;code&gt;open_time&lt;/code&gt; and keep only minutes where both markets had at least one trade (&lt;code&gt;trade_count &amp;gt; 0&lt;/code&gt;). A pool without swaps keeps its last price; mixing a stale close with a fresh one manufactures a fake gap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exact decimals.&lt;/strong&gt; Prices are stored as &lt;code&gt;NUMERIC&lt;/code&gt;, computed from raw token amounts truncated at 24 decimal places. No floats until the chart.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native orientation kept.&lt;/strong&gt; In the WETH/USDC pool, USDC sorts before WETH by address, so &lt;code&gt;sqrtPriceX96&lt;/code&gt; gives WETH per USDC. We invert explicitly and label the pair as the pool has it, instead of silently flipping.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Know the noise floor.&lt;/strong&gt; Each pool has its own fee tier and its own last-trade side, so two healthy pools disagree by a few basis points. Over 20 days the median implied USDT was 0.9989 USDC and 90% of hours sat between 0.9985 and 0.9993. Anything inside that band is noise, not a depeg.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;close&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;mk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'WETHUSDC'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;close&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;mk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'WETHUSDT'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;market_kline&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;
  &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;trade_market&lt;/span&gt; &lt;span class="n"&gt;mk&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;mk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;market_id&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retention_group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'1m'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trade_count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;mk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;venue_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;uniswap_v3&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&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="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;minutes_below_099&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;MIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                             &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;worst&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What 20 days look like
&lt;/h2&gt;

&lt;p&gt;October 15 – November 3, 2026:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measure&lt;/th&gt;
&lt;th&gt;Median&lt;/th&gt;
&lt;th&gt;90% of hours&lt;/th&gt;
&lt;th&gt;Beyond threshold&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;USDT in USDC&lt;/td&gt;
&lt;td&gt;0.9989&lt;/td&gt;
&lt;td&gt;0.9985 – 0.9993&lt;/td&gt;
&lt;td&gt;below 0.99: 42 of 17,152 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WBTC in BTC&lt;/td&gt;
&lt;td&gt;−0.16%&lt;/td&gt;
&lt;td&gt;−0.22% – −0.04%&lt;/td&gt;
&lt;td&gt;beyond ±1%: 45 of 9,366 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every one of those 42 and 45 minutes falls on the evening of October 29.&lt;/p&gt;

&lt;h2&gt;
  
  
  The evening of October 29
&lt;/h2&gt;

&lt;p&gt;Attackers drained Binance hot wallets from about 20:05 UTC and Binance suspended withdrawals. One-minute closes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;UTC&lt;/th&gt;
&lt;th&gt;Binance ETH/USDT&lt;/th&gt;
&lt;th&gt;Uni v3 WETH/USDC&lt;/th&gt;
&lt;th&gt;Uni v3 WETH/USDT&lt;/th&gt;
&lt;th&gt;implied USDT&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;20:30&lt;/td&gt;
&lt;td&gt;2,318&lt;/td&gt;
&lt;td&gt;2,315&lt;/td&gt;
&lt;td&gt;2,318&lt;/td&gt;
&lt;td&gt;0.999&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20:34&lt;/td&gt;
&lt;td&gt;2,298&lt;/td&gt;
&lt;td&gt;2,336&lt;/td&gt;
&lt;td&gt;2,342&lt;/td&gt;
&lt;td&gt;0.997&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20:36&lt;/td&gt;
&lt;td&gt;2,302&lt;/td&gt;
&lt;td&gt;2,520&lt;/td&gt;
&lt;td&gt;2,536&lt;/td&gt;
&lt;td&gt;0.994&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20:45&lt;/td&gt;
&lt;td&gt;2,264&lt;/td&gt;
&lt;td&gt;2,662&lt;/td&gt;
&lt;td&gt;2,834&lt;/td&gt;
&lt;td&gt;0.939&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21:09&lt;/td&gt;
&lt;td&gt;2,266&lt;/td&gt;
&lt;td&gt;2,753&lt;/td&gt;
&lt;td&gt;3,040&lt;/td&gt;
&lt;td&gt;0.906&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21:20&lt;/td&gt;
&lt;td&gt;2,282&lt;/td&gt;
&lt;td&gt;2,566&lt;/td&gt;
&lt;td&gt;2,610&lt;/td&gt;
&lt;td&gt;0.983&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21:35&lt;/td&gt;
&lt;td&gt;2,291&lt;/td&gt;
&lt;td&gt;2,313&lt;/td&gt;
&lt;td&gt;2,327&lt;/td&gt;
&lt;td&gt;0.994&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two separate effects stack in the pools: ETH itself was bid on-chain (USDC pools about 21% above Binance), and USDT was discounted against USDC (USDT pools 34–40% above Binance). Twelve ETH pools crossed a 2% gap between 20:34 and 20:39 and almost all peaked at 21:09. Bitcoin shows the same split more mildly: WBTC/USDC stayed within about 1–2% of Binance, WBTC/USDT reached 11% above it.&lt;/p&gt;

&lt;p&gt;Why it lasted an hour: with withdrawals closed, nobody could move inventory from Binance to Ethereum, so the cross-venue arbitrage that normally closes a 1% gap in minutes was cut in one direction. Prices tell you the result, not who traded; we make no claim about the attacker's flows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pitfalls we hit
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hourly candles hide the event.&lt;/strong&gt; The worst hourly ratio was 0.9834; the worst minute was 0.9054. Aggregate after you look, not before.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Last price is not a quote.&lt;/strong&gt; A thin pool's close can be one small swap. For "could I have traded there" you need depth, not a candle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interpolation lies.&lt;/strong&gt; Never forward-fill a pool's price into minutes without trades; show its age instead.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] read a missing cross rate through a shared base asset, same protocol on both legs&lt;/li&gt;
&lt;li&gt;[ ] join on the same minute and require trades on both legs&lt;/li&gt;
&lt;li&gt;[ ] keep exact decimals and the pool's native orientation&lt;/li&gt;
&lt;li&gt;[ ] measure the noise floor before calling anything a depeg&lt;/li&gt;
&lt;li&gt;[ ] look at 1-minute data before trusting hourly aggregates&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Disclosure: we build &lt;a href="https://stargate.zone/about" rel="noopener noreferrer"&gt;StarGate&lt;/a&gt;; its Trade screen shows these markets as separate lines on one chart. This post was drafted by an AI assistant from our observer's code and production data.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ethereum</category>
      <category>defi</category>
      <category>sql</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Publishing a daily LLM market note in 22 languages without letting it invent facts</title>
      <dc:creator>StarGate</dc:creator>
      <pubDate>Wed, 04 Nov 2026 14:12:32 +0000</pubDate>
      <link>https://dev.to/stargatezone/publishing-a-daily-llm-market-note-in-22-languages-without-letting-it-invent-facts-c19</link>
      <guid>https://dev.to/stargatezone/publishing-a-daily-llm-market-note-in-22-languages-without-letting-it-invent-facts-c19</guid>
      <description>&lt;p&gt;Every evening our pipeline publishes one market note for BTC, ETH, GRAM and DOGE: what moved, what drove it, and three scenarios for the next 24 hours with probabilities. It has run daily since September 25, 2026: 39 issues so far, none withdrawn. The interesting part is not the prompt. It is everything around the model that decides whether a draft is allowed to reach readers. Here is that part.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule: two authorities for facts
&lt;/h2&gt;

&lt;p&gt;A market note mixes two kinds of facts, and we never let one prove the other:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Claim&lt;/th&gt;
&lt;th&gt;Authority&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Something happened&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;sourceEvidence&lt;/code&gt;: articles from an allow-list of public feeds&lt;/td&gt;
&lt;td&gt;"Binance began the second withdrawal phase"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A number&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;marketEvidence&lt;/code&gt;: exchange quotes read at writing time, with &lt;code&gt;observedAt&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;"BTC $77,592.96, +0.851% in 24 h"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The writer and the reviewer receive the same two evidence sets. A news article that mentions a price does not make that price true, and a market quote does not prove an event. Early on we required publisher articles to repeat exchange numbers, and the reviewer rejected perfectly good drafts as &lt;code&gt;review_unsupported_market_data&lt;/code&gt;. Splitting the authorities removed those false rejections without loosening anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources are an allow-list
&lt;/h2&gt;

&lt;p&gt;Six feeds today: Cointelegraph, Decrypt, the Ethereum Foundation blog, SEC press releases and two Federal Reserve feeds. When a feed started answering our fetcher with a Cloudflare challenge, we disabled the source instead of solving the challenge: solving it would be circumvention, not fetching. Ingestion only stores claimless references; no model call happens per article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strict structured output, and its traps
&lt;/h2&gt;

&lt;p&gt;Every stage returns JSON against a strict schema. Two lessons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The provider's strict subset is narrower than JSON Schema.&lt;/strong&gt; A schema with &lt;code&gt;uniqueItems&lt;/code&gt; was refused with &lt;code&gt;400 invalid_json_schema&lt;/code&gt; before the model was reached, so extraction failed on every run while other stages passed. A unit test now scans every stage schema for keywords the subset rejects and fails with the stage and keyword.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Some invariants cannot live in the schema.&lt;/strong&gt; "Probabilities total 100" or "&lt;code&gt;eventAt&lt;/code&gt; is ISO-8601 or null" are stated in the instruction &lt;em&gt;and&lt;/em&gt; checked in code after parsing. Leaving them only in code meant paying for a schema-valid answer and then discarding it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fail closed, and say why
&lt;/h2&gt;

&lt;p&gt;A draft passes deterministic checks (citations point at the provided sources, numbers match &lt;code&gt;marketEvidence&lt;/code&gt;, probabilities sum to 100, required sections exist) and then an independent review step. Anything else is not published.&lt;/p&gt;

&lt;p&gt;The first version refused candidates with a generic &lt;code&gt;content_validation_failed&lt;/code&gt;, and production could not tell which of seventeen checks fired. Now every refusal is a typed reason (an enum case such as &lt;code&gt;citation_outside_cluster&lt;/code&gt;) stored on the claim row. We deliberately do not log the exception message: a fault raised over article text or a prompt can carry that text, so logs carry only a bounded class-name chain of the fault.&lt;/p&gt;

&lt;h2&gt;
  
  
  Translations: same facts, new words
&lt;/h2&gt;

&lt;p&gt;The English issue is published first. Then each of the 21 other locales gets its own job. Application code copies the canonical fields (numbers, probabilities, citation URLs, asset keys) into every language; the model only translates prose. One call for thirteen locales once produced 11,484 of 12,000 output tokens in 119.5 s against a 120 s timeout, so translation now runs in separate calls instead of one giant one.&lt;/p&gt;

&lt;p&gt;A deterministic normalizer runs before storage: GRAM is the current name of the coin formerly called Toncoin, and sources still use the old ticker. Whole-word &lt;code&gt;TON&lt;/code&gt; and &lt;code&gt;Toncoin&lt;/code&gt; in model-written text become &lt;code&gt;GRAM&lt;/code&gt;; URLs stay untouched.&lt;/p&gt;

&lt;h2&gt;
  
  
  A hard daily budget
&lt;/h2&gt;

&lt;p&gt;There is one money limit: a fixed daily ceiling. Before every paid call the gateway computes a provable input-token ceiling and reserves &lt;code&gt;input ceiling × input rate + output cap × output rate&lt;/code&gt;. Nothing is sent unless the reservation succeeds. When a response carries exact usage, the reservation shrinks to the actual cost before the next call. A definitive 4xx is settled at actual usage (no inference ran); a 5xx keeps the worst case, because it can fail after inference started.&lt;/p&gt;

&lt;p&gt;The bug that taught us this: a flag meant "a paid call happened in this run" and was never cleared, so any later failure was billed as uncertain worst case. One broken run consumed the whole day's budget with zero publications, two days in a row. The flag is now "a paid call is in flight" and is cleared only after exact usage is confirmed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Delivery is not part of generation
&lt;/h2&gt;

&lt;p&gt;Publishing writes the document; delivery is a separate rail. The site renders the document in the reader's locale, an RSS feed exists per locale, and a bot digest is queued as its own job after translations arrive (or after a two-hour wait, falling back to English). Views are counted once per visitor per day by the browser after the article is on screen, not by the server-side render, so crawlers do not inflate them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] separate the authority for events from the authority for numbers&lt;/li&gt;
&lt;li&gt;[ ] allow-list sources; disable a source that blocks you, do not bypass it&lt;/li&gt;
&lt;li&gt;[ ] test your schemas against the provider's strict subset&lt;/li&gt;
&lt;li&gt;[ ] state cross-field invariants in the instruction and check them in code&lt;/li&gt;
&lt;li&gt;[ ] store a typed refusal reason per draft; never log raw content&lt;/li&gt;
&lt;li&gt;[ ] copy numbers and links in code; let the model translate only prose&lt;/li&gt;
&lt;li&gt;[ ] reserve the worst case before each call; shrink to actual usage after it&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Disclosure: we build &lt;a href="https://stargate.zone/about" rel="noopener noreferrer"&gt;StarGate&lt;/a&gt;, whose Market Opinion is the pipeline described here. This post was drafted by an AI assistant from the pipeline's code and documentation; the numbers come from its production history.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>cryptocurrency</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Reading DEX prices from Ethereum logs: Uniswap v2/v3/v4, Balancer, Curve, Fluid and DODO next to Binance</title>
      <dc:creator>StarGate</dc:creator>
      <pubDate>Wed, 04 Nov 2026 10:40:10 +0000</pubDate>
      <link>https://dev.to/stargatezone/reading-dex-prices-from-ethereum-logs-uniswap-v2v3v4-balancer-curve-fluid-and-dodo-next-to-496g</link>
      <guid>https://dev.to/stargatezone/reading-dex-prices-from-ethereum-logs-uniswap-v2v3v4-balancer-curve-fluid-and-dodo-next-to-496g</guid>
      <description>&lt;p&gt;We wanted one chart that shows BTC and ETH from Binance and from every large Ethereum pool as separate lines, with no averaging and no invented points. Binance is the easy half: its &lt;code&gt;@trade&lt;/code&gt; stream gives price, quantity and time per trade. The pools are harder: eight protocol families, each with its own events, its own math and its own idea of what "the price" is. This post is what we learned building that observer in Rust, with numbers from its own data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two kinds of price
&lt;/h2&gt;

&lt;p&gt;For every pool we keep two things apart:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Trade price&lt;/strong&gt; — the ratio of what actually changed hands in one swap: &lt;code&gt;quote_amount / base_amount&lt;/code&gt;, taken from the swap's two legs. It produces volume.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spot price&lt;/strong&gt; — the pool's marginal price right after an event (or at block end). It produces the line on the chart and never produces volume.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A swap whose legs do not move in opposite directions is not a trade and is skipped. All ratios are exact decimals truncated (not rounded) at 24 places; JSON carries them as strings. No &lt;code&gt;f64&lt;/code&gt; anywhere a trader reads a number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Per family
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Family&lt;/th&gt;
&lt;th&gt;Events&lt;/th&gt;
&lt;th&gt;Spot price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Uniswap v2 and forks (SushiSwap)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Sync&lt;/code&gt;, &lt;code&gt;Swap&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;reserves after each &lt;code&gt;Sync&lt;/code&gt;: &lt;code&gt;reserve_quote / reserve_base&lt;/code&gt;, scaled by decimals&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Uniswap v3&lt;/td&gt;
&lt;td&gt;pool &lt;code&gt;Swap&lt;/code&gt; (pool-signed amounts)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;sqrtPriceX96&lt;/code&gt; of each swap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Uniswap v4&lt;/td&gt;
&lt;td&gt;PoolManager &lt;code&gt;Swap&lt;/code&gt;, &lt;code&gt;topic1&lt;/code&gt; = PoolId (swapper-signed amounts)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;sqrtPriceX96&lt;/code&gt; of each swap; the PoolKey must hash to the PoolId; native ETH is &lt;code&gt;0x0&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Balancer v2 (weighted)&lt;/td&gt;
&lt;td&gt;Vault &lt;code&gt;Swap&lt;/code&gt;, &lt;code&gt;PoolBalanceChanged&lt;/code&gt;, &lt;code&gt;PoolBalanceManaged&lt;/code&gt;, &lt;code&gt;topic1&lt;/code&gt; = poolId&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;getPoolTokens&lt;/code&gt; + &lt;code&gt;getNormalizedWeights&lt;/code&gt; at N−1, replayed per event: &lt;code&gt;(Bq/Wq)/(Bb/Wb)&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Balancer v1&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;LOG_SWAP&lt;/code&gt;, &lt;code&gt;LOG_JOIN&lt;/code&gt;, &lt;code&gt;LOG_EXIT&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;getBalance&lt;/code&gt; + &lt;code&gt;getDenormalizedWeight&lt;/code&gt; at N−1, replayed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Curve&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;TokenExchange&lt;/code&gt; variants, only the market's coin pair counts&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;get_dy(base, quote, 10^(dec_base−3))&lt;/code&gt; at block end, which includes the pool fee&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fluid DEX&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Swap(bool swap0to1, …)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;resolver &lt;code&gt;getDexPricesAndExchangePrices&lt;/code&gt;, word 0 (token1 per token0 × 1e27), at block end&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DODO v2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DODOSwap&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;getMidPrice() / 1e18&lt;/code&gt;, scaled by decimals, at block end&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three details bit us:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;sqrtPriceX96&lt;/code&gt; is token1 per token0, and token order is by address.&lt;/strong&gt; &lt;code&gt;price = (sqrtPriceX96 / 2^96)^2 × 10^(dec0 − dec1)&lt;/code&gt;. In the main WETH/USDC pool USDC (&lt;code&gt;0xa0b8…&lt;/code&gt;) sorts before WETH (&lt;code&gt;0xc02a…&lt;/code&gt;), so the formula gives WETH per USDC and has to be inverted to get dollars per ETH. We keep the pool's native pair (WETH/USDC) on the label and mark the inversion explicitly instead of silently flipping it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;v3 and v4 sign amounts differently.&lt;/strong&gt; v3 amounts are signed from the pool's point of view, v4 from the swapper's. Same field names, opposite signs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared contracts.&lt;/strong&gt; Balancer v2 Vault and Uniswap v4 PoolManager emit events for thousands of pools. We filter &lt;code&gt;eth_getLogs&lt;/code&gt; by the contract address only (no topic filter), route a pool-scoped event by the pool id in &lt;code&gt;topic1&lt;/code&gt;, ignore other pool ids, and treat an unknown &lt;code&gt;topic0&lt;/code&gt; of an observed contract as an error that stops the block. Silently skipping unknown events is how you end up with a price that drifted for a week without anyone noticing.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  State reads pinned to a block hash
&lt;/h2&gt;

&lt;p&gt;Some families cannot be priced from logs alone (Balancer weights, Curve, Fluid, DODO). We read their state with one Multicall3 &lt;code&gt;aggregate3&lt;/code&gt; per block, either at the parent (&lt;code&gt;N−1&lt;/code&gt;, the pre-state to replay events on) or at &lt;code&gt;N&lt;/code&gt; (block-end state). Every call is pinned by &lt;a href="https://eips.ethereum.org/EIPS/eip-1898" rel="noopener noreferrer"&gt;EIP-1898&lt;/a&gt; &lt;code&gt;{"blockHash": …, "requireCanonical": true}&lt;/code&gt;, never by number: if a reorg happens between reading the header and the call, the call fails instead of mixing two branches.&lt;/p&gt;

&lt;p&gt;Curve and Fluid pools can move without their own events (oracle, amplification, weights), so every 5th block we also probe them and store a "probe" snapshot. A probe moves the line; it never creates a trade or volume.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reorgs
&lt;/h2&gt;

&lt;p&gt;Within 64 blocks of the head we fetch each block by number and check &lt;code&gt;parentHash&lt;/code&gt; against the last committed block. On a mismatch we walk back to the fork point, and one database transaction retracts the orphaned trades, restores the affected candles and moves the checkpoint. The checkpoint never moves past uncommitted data, so a SIGTERM in the middle of a block leaves nothing half written.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gaps are gaps
&lt;/h2&gt;

&lt;p&gt;A pool with no swaps for an hour keeps its last price, and the chart says how old it is. A source that stopped delivering is shown as unavailable. We never interpolate or forward-fill a price as if it were fresh. In our data one Balancer v1 WBTC/USDC pool had no trades for four days; it is still listed, with its age.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the data shows
&lt;/h2&gt;

&lt;p&gt;From 5-minute closes, October 28 – November 4, 2026:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Binance ETH/USDT vs Uniswap v3 WETH/USDC: median gap 0.11%, 95th percentile 0.19%.&lt;/li&gt;
&lt;li&gt;Binance BTC/USDT vs Uniswap v3 WBTC/USDC: median 0.21%, 95th percentile 0.35%. Two differences stack here: BTC vs WBTC and USDT vs USDC.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The exception is October 29, 2026, the day of the Binance hot-wallet hack. Prices moved together until about 20:30 UTC, then split: at 20:45 UTC Uniswap v3 showed about $2,744 per ETH and Binance about $2,262, a 21% gap. It stayed above 10% for about 45 minutes and was under 1% again around 21:35 UTC. With withdrawals suspended, inventory could not move between the exchange and the chain, so arbitrage could not close the gap at its usual speed. An averaged price feed would have shown a single number in the middle and hidden both markets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist if you build something similar
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] keep trade price and spot price separate; only trades make volume&lt;/li&gt;
&lt;li&gt;[ ] use exact decimals, truncate, never &lt;code&gt;f64&lt;/code&gt; for prices&lt;/li&gt;
&lt;li&gt;[ ] check token order before reading &lt;code&gt;sqrtPriceX96&lt;/code&gt;, and label inversions&lt;/li&gt;
&lt;li&gt;[ ] handle v3 vs v4 amount signs&lt;/li&gt;
&lt;li&gt;[ ] fail on unknown events of an observed contract&lt;/li&gt;
&lt;li&gt;[ ] pin &lt;code&gt;eth_call&lt;/code&gt; to the block hash (EIP-1898)&lt;/li&gt;
&lt;li&gt;[ ] retract trades and restore candles on reorgs in one transaction&lt;/li&gt;
&lt;li&gt;[ ] show the age of the last price instead of filling gaps&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Disclosure: we build &lt;a href="https://stargate.zone/about" rel="noopener noreferrer"&gt;StarGate&lt;/a&gt;, a wallet, swap and bridge service whose app has a Trade screen with these markets. This post was drafted by an AI assistant from our observer's code and documentation, and the numbers come from its production data.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ethereum</category>
      <category>defi</category>
      <category>web3</category>
      <category>rust</category>
    </item>
    <item>
      <title>EIP-7702 and hot wallets: what the $6.3B Binance hack teaches about signing infrastructure</title>
      <dc:creator>StarGate</dc:creator>
      <pubDate>Sat, 31 Oct 2026 00:57:53 +0000</pubDate>
      <link>https://dev.to/stargatezone/eip-7702-and-hot-wallets-what-the-63b-binance-hack-teaches-about-signing-infrastructure-24n6</link>
      <guid>https://dev.to/stargatezone/eip-7702-and-hot-wallets-what-the-63b-binance-hack-teaches-about-signing-infrastructure-24n6</guid>
      <description>&lt;p&gt;On October 29, 2026, about $6.3 billion left Binance hot wallets on ten chains in roughly half an hour. On Ethereum, the biggest part, the attacker did not need a single raw transfer signed by Binance: one &lt;a href="https://eips.ethereum.org/EIPS/eip-7702" rel="noopener noreferrer"&gt;EIP-7702&lt;/a&gt; authorization turned an exchange hot wallet into an account that obeyed the attacker's contract. If you run any service that signs Ethereum transactions from an EOA, this post covers what happened on-chain, why 7702 authorizations need their own controls, and a checklist for your signer.&lt;/p&gt;

&lt;p&gt;Figures and timeline below come from the &lt;a href="https://www.trmlabs.com/resources/blog/north-korea-steals-usd-6-36-billion-from-binance-in-the-largest-hack-in-history" rel="noopener noreferrer"&gt;TRM Labs analysis&lt;/a&gt; and the &lt;a href="https://www.chainalysis.com/blog/binance-exploit-latest-findings-and-response/" rel="noopener noreferrer"&gt;Chainalysis update&lt;/a&gt; as of October 30–31, 2026. Binance has not published a root cause yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  EIP-7702 in two minutes
&lt;/h2&gt;

&lt;p&gt;EIP-7702 shipped with the Pectra upgrade on May 7, 2025. It adds transaction type &lt;code&gt;0x04&lt;/code&gt; (&lt;code&gt;SET_CODE_TX_TYPE&lt;/code&gt;) with an &lt;code&gt;authorization_list&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;authorization_list = [[chain_id, address, nonce, y_parity, r, s], ...]
msg = keccak(0x05 || rlp([chain_id, address, nonce]))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a tuple is valid, the protocol writes a delegation indicator &lt;code&gt;0xef0100 || address&lt;/code&gt; into the code of the signing EOA (the authority) and increments its nonce. From then on, calls to the EOA execute the code at &lt;code&gt;address&lt;/code&gt; in the EOA's context. Key properties from the spec:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It persists.&lt;/strong&gt; The delegation stays until another authorization replaces it; an authorization to the zero address clears it. It is not rolled back even if the transaction itself fails.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anyone can submit it.&lt;/strong&gt; The authorization is signed by the EOA, but the type-4 transaction carrying it can be sent by any account. The EOA does not need to send anything itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The nonce must match exactly.&lt;/strong&gt; Step 6 of the spec: "Verify the nonce of &lt;code&gt;authority&lt;/code&gt; is equal to &lt;code&gt;nonce&lt;/code&gt;." A tuple with a stale nonce is skipped, not rejected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;chain_id = 0&lt;/code&gt; means every chain.&lt;/strong&gt; "When universal deployment is preferred, simply set chain ID to 0."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The delegate has full control.&lt;/strong&gt; "The code specified by an authorization has unrestricted access to the account."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The spec is blunt about wallets: "Applications &lt;strong&gt;must not&lt;/strong&gt; expect that they can suggest the user sign an authorization", and "There is no safe way to provide this interface".&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened on Ethereum
&lt;/h2&gt;

&lt;p&gt;According to TRM:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Staging.&lt;/strong&gt; About 36 hours before the theft the operator wallet was funded with small Tornado Cash withdrawals. The attack code was tested on two wallets, then rehearsed on a third.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fifty authorizations.&lt;/strong&gt; The attacker held 50 pre-signed 7702 authorizations for the same hot wallet, covering 50 consecutive nonces. A busy exchange hot wallet sends transactions constantly, so its nonce moves; with 50 candidates, one would still match when the attack transaction landed. "One matched."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delegation to a sweeper.&lt;/strong&gt; The authorization pointed the hot wallet to a contract that "accepts instructions only from the operator". About three and a half minutes later the first sweep took 167,464 ETH and 63 tokens, including 1.615 billion USDT.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-sweeps.&lt;/strong&gt; Because the delegation persisted, new deposits arriving at the hot wallet were swept again, six more times over the next few hours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smart account executor.&lt;/strong&gt; The same hot wallet was the executor of a Binance smart account, which let the attacker also pull 823 million USDC.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On the other nine chains (Bitcoin, Tron, XRP Ledger, Solana, Arbitrum, Optimism, Base, Avalanche, Polygon) the wallets simply signed ordinary transfers to attacker addresses. TRM's reading: wallets with "different chains, key types and signing schemes" all signed for the attacker within about half an hour, which "points to a compromise of Binance's hot wallet signing infrastructure", and the batch of 50 signatures suggests access "may have been limited in time, or that signatures had to be requested through a system". Binance's initial statement mentioned a compromised DevOps engineer's laptop and "no evidence of any wallet private key leak".&lt;/p&gt;

&lt;p&gt;The key lesson for builders: the keys may never have left the HSM or signer. The signer was asked to sign, and it signed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a 7702 authorization is not "just another signature"
&lt;/h2&gt;

&lt;p&gt;A signing service usually reasons about transactions: destination, value, calldata, limits per period. A 7702 authorization is different:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it has &lt;strong&gt;no value and no destination&lt;/strong&gt; to check against a limit;&lt;/li&gt;
&lt;li&gt;it is &lt;strong&gt;not a transaction&lt;/strong&gt;, so a policy engine that only inspects transactions may not see it at all;&lt;/li&gt;
&lt;li&gt;one signature gives the delegate control over &lt;strong&gt;all current and future assets&lt;/strong&gt; of the account, until revoked;&lt;/li&gt;
&lt;li&gt;it can be &lt;strong&gt;pre-signed for future nonces&lt;/strong&gt; and kept for later, so a short window of access becomes a long-lived capability.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, for a custodial EOA, signing a 7702 authorization is equivalent to handing over the key.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist for anyone who signs from an EOA
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;In the signer:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Refuse EIP-7702 authorizations by default. If your product does not use smart-account features, the signer should have no code path that produces the &lt;code&gt;0x05 || rlp([chain_id, address, nonce])&lt;/code&gt; digest at all.&lt;/li&gt;
&lt;li&gt;If you do need delegation, allow-list the exact delegate &lt;code&gt;address&lt;/code&gt;, never accept &lt;code&gt;chain_id = 0&lt;/code&gt;, and require a separate approval path with human sign-off.&lt;/li&gt;
&lt;li&gt;Never sign an authorization for a nonce other than the account's current nonce. A request for a range of future nonces is an indicator of compromise, not an edge case.&lt;/li&gt;
&lt;li&gt;Treat any raw-digest signing endpoint ("sign these 32 bytes") as equivalent to key export. Sign only structured, decoded requests that your policy engine understands.&lt;/li&gt;
&lt;li&gt;Separate signing infrastructure by chain and wallet tier, so one compromised path does not reach every network.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;In monitoring:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Alert on any change of code at your EOAs. &lt;code&gt;eth_getCode&lt;/code&gt; on a plain EOA returns &lt;code&gt;0x&lt;/code&gt;; a delegated one returns 23 bytes starting with &lt;code&gt;0xef0100&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Watch for type-4 transactions that include your addresses as authorities, sent by anyone.&lt;/li&gt;
&lt;li&gt;Have a pre-signed or ready-to-sign authorization to the zero address for each hot wallet, so you can clear a delegation in one transaction during an incident.&lt;/li&gt;
&lt;li&gt;Keep hot wallet balances small and refill from cold storage on a schedule: a persistent delegation keeps sweeping every new deposit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;In incident response:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stablecoin issuers can freeze; native ETH and BTC cannot. In this case Tether, Circle, Ripple and others froze about $2.7 billion within hours, while more than 420,000 ETH and 18,270 BTC remained with the attacker, per TRM.&lt;/li&gt;
&lt;li&gt;Have issuer contacts and freeze request templates ready before you need them.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to check an address today
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RPC_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"jsonrpc":"2.0","id":1,"method":"eth_getCode","params":["0xYourHotWallet","latest"]}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;"result":"0x"&lt;/code&gt; means no code. A result like &lt;code&gt;0xef0100&lt;/code&gt; followed by 20 bytes means the EOA is delegated to that address, and you should know exactly why.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;EIP-7702 made "sign one message, lose the account" possible for any EOA, including custodial hot wallets.&lt;/li&gt;
&lt;li&gt;Signers must understand authorizations as a separate, high-risk object, not as a transaction or a generic digest.&lt;/li&gt;
&lt;li&gt;Monitoring for code at your own EOAs is one RPC call; it is worth running on every block.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Disclosure: we build &lt;a href="https://stargate.zone/about" rel="noopener noreferrer"&gt;StarGate&lt;/a&gt;, a Telegram bot and web app that swaps Telegram Stars for GRAM, USDT, ETH and BTC; we are not affiliated with Binance. This post was drafted by an AI assistant from the EIP-7702 specification and the TRM Labs and Chainalysis reports linked above, and every quote was checked against those pages.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ethereum</category>
      <category>security</category>
      <category>web3</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>Telegram gift auctions in the MTProto API: rounds, bid position and refunds</title>
      <dc:creator>StarGate</dc:creator>
      <pubDate>Thu, 29 Oct 2026 08:56:17 +0000</pubDate>
      <link>https://dev.to/stargatezone/telegram-gift-auctions-in-the-mtproto-api-rounds-bid-position-and-refunds-1io</link>
      <guid>https://dev.to/stargatezone/telegram-gift-auctions-in-the-mtproto-api-rounds-bid-position-and-refunds-1io</guid>
      <description>&lt;p&gt;Since November 2025, new limited Telegram gifts are released through auctions instead of a fixed-price sale. Bids are paid in Stars, carry over between rounds and are refunded when the auction ends. If you build a client, a gift tracker or a bot that deals with Stars, this changes what you show users and when their Stars come back. This post walks through the &lt;a href="https://core.telegram.org/api/auctions" rel="noopener noreferrer"&gt;auction docs&lt;/a&gt; and the related &lt;a href="https://core.telegram.org/api/config" rel="noopener noreferrer"&gt;client configuration&lt;/a&gt; values.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spotting an auction gift
&lt;/h2&gt;

&lt;p&gt;An auctioned gift is a &lt;code&gt;starGift&lt;/code&gt; with the &lt;code&gt;auction&lt;/code&gt; flag set, plus &lt;code&gt;auction_slug&lt;/code&gt;, &lt;code&gt;gifts_per_round&lt;/code&gt; and &lt;code&gt;auction_start_date&lt;/code&gt;. The docs are strict about it: such a gift "is auction-only: it must &lt;strong&gt;not&lt;/strong&gt; be bought through the normal gift sending flow". The slug builds a &lt;code&gt;t.me/auction/&amp;lt;slug&amp;gt;&lt;/code&gt; deep link.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading the state
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;payments.getStarGiftAuctionState(auction, version)&lt;/code&gt; returns the gift, the auction state and the current user's state, and subscribes the client to &lt;code&gt;updateStarGiftAuctionState&lt;/code&gt; for &lt;code&gt;timeout&lt;/code&gt; seconds. Call it again after &lt;code&gt;timeout&lt;/code&gt; to keep receiving updates.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;payments.getStarGiftActiveAuctions(hash)&lt;/code&gt; returns every unfinished auction the user has bid on, including ones where the bid was returned. Clients use it at startup to show an auction badge.&lt;/li&gt;
&lt;li&gt;Apply a &lt;code&gt;starGiftAuctionState&lt;/code&gt; only if its &lt;code&gt;version&lt;/code&gt; is bigger than the cached one: the server "may sometimes return slightly outdated cached versions". A &lt;code&gt;starGiftAuctionStateFinished&lt;/code&gt; has no version and replaces any cached state.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Rounds
&lt;/h2&gt;

&lt;p&gt;The auction has &lt;code&gt;total_rounds&lt;/code&gt; rounds. Each round gives &lt;code&gt;gifts_per_round&lt;/code&gt; gifts to the top bidders of that round, so the total supply is &lt;code&gt;gifts_per_round * total_rounds&lt;/code&gt;. &lt;code&gt;rounds&lt;/code&gt; describes them compactly: each &lt;code&gt;StarGiftAuctionRound&lt;/code&gt; covers rounds from its &lt;code&gt;num&lt;/code&gt; up to the next entry's &lt;code&gt;num - 1&lt;/code&gt;. An extendable round (&lt;code&gt;starGiftAuctionRoundExtendable&lt;/code&gt;) has &lt;code&gt;extend_top&lt;/code&gt; and &lt;code&gt;extend_window&lt;/code&gt;: a change in the top &lt;code&gt;extend_top&lt;/code&gt; positions prolongs the round by &lt;code&gt;extend_window&lt;/code&gt; seconds, so a late bid near the top extends the round instead of closing it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gifts_left&lt;/code&gt; is the live count of gifts still to distribute. It overrides &lt;code&gt;starGift.availability_remains&lt;/code&gt;, which lags behind because updates do not refresh it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bids
&lt;/h2&gt;

&lt;p&gt;A bid is a normal Stars payment: &lt;code&gt;payments.getPaymentForm&lt;/code&gt; with &lt;code&gt;inputInvoiceStarGiftAuctionBid&lt;/code&gt; (&lt;code&gt;peer&lt;/code&gt;, &lt;code&gt;gift_id&lt;/code&gt;, &lt;code&gt;bid_amount&lt;/code&gt;, optional &lt;code&gt;message&lt;/code&gt; and &lt;code&gt;hide_name&lt;/code&gt;; &lt;code&gt;update_bid&lt;/code&gt; to raise an existing bid). Things that matter for UX:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A bid "cannot be retracted". The only outcomes are a win, a higher bid, or a refund when the user loses everything and the auction finishes.&lt;/li&gt;
&lt;li&gt;A bid outside the top &lt;code&gt;N&lt;/code&gt; of a round carries over to the next round automatically.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;starGiftAuctionUserState.min_bid_amount&lt;/code&gt;, when present, overrides the auction-wide &lt;code&gt;min_bid_amount&lt;/code&gt; for this user.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;returned&lt;/code&gt; means the bid fell so far behind that it can never win, even if nobody bids again. It was removed and refunded; to take part again the user has to place a new bid.&lt;/li&gt;
&lt;li&gt;Bids appear in the Stars transaction history with the &lt;code&gt;stargift_auction_bid&lt;/code&gt; flag.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Estimating the user's position
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;bid_levels&lt;/code&gt; holds exact positions for the first 100 places and coarser checkpoints after that (100, 200 … 1,000, 2,000 … &lt;code&gt;gifts_left&lt;/code&gt;). Equal amounts rank by time: the earlier bid is higher. The documented algorithm, as code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;auction_position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;my_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;my_id&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;top_bidders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;top_bidders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bid_levels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# ascending by position
&lt;/span&gt;        &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;user_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bid_amount&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;user_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bid_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; \
                &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;user_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bid_amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bid_levels&lt;/span&gt;&lt;span class="p"&gt;[&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="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;expected_gift_number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_gift_num&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Positions from coarse checkpoints are approximate, so round the displayed number accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Winning, and what you cannot do afterwards
&lt;/h2&gt;

&lt;p&gt;Won gifts arrive as &lt;code&gt;messageActionStarGift&lt;/code&gt; with &lt;code&gt;auction_acquired&lt;/code&gt; set; &lt;code&gt;gift_num&lt;/code&gt; is the collectible number. &lt;code&gt;payments.getStarGiftAuctionAcquiredGifts&lt;/code&gt; lists everything the user won, with bid amount, round and position.&lt;/p&gt;

&lt;p&gt;Two limits to surface in the UI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Gifts acquired through collectible gift auction cannot be converted back into Telegram Stars."&lt;/li&gt;
&lt;li&gt;After the auction, &lt;code&gt;starGiftAuctionStateFinished&lt;/code&gt; gives &lt;code&gt;average_price&lt;/code&gt;, and optionally &lt;code&gt;listed_count&lt;/code&gt; (on resale in Telegram, fetch with &lt;code&gt;payments.getResaleStarGifts&lt;/code&gt;) and &lt;code&gt;fragment_listed_count&lt;/code&gt; with &lt;code&gt;fragment_listed_url&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Resale economics from the client config
&lt;/h2&gt;

&lt;p&gt;If a user resells the gift, the seller receives &lt;code&gt;price * permille / 1000&lt;/code&gt;. The documented config example has:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stars_stargift_resale_commission_permille&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;td&gt;seller gets 80% of a price in Stars&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ton_stargift_resale_commission_permille&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;900&lt;/td&gt;
&lt;td&gt;seller gets 90% of a price in GRAM (nanograms)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stars_stargift_resale_amount_min&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;125&lt;/td&gt;
&lt;td&gt;minimum resale price in Stars&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stars_stargift_resale_amount_max&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;100000&lt;/td&gt;
&lt;td&gt;maximum resale price in Stars&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read these from &lt;code&gt;help.getAppConfig&lt;/code&gt; at runtime instead of hardcoding them: they are configuration, not protocol constants.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for Stars flows
&lt;/h2&gt;

&lt;p&gt;Auctions move Stars in bursts. Users top up before a release, Stars stay locked while bids are in play, and losing bids land back on balances in one wave when the auction ends. Resales paid in Stars add more. If your bot accepts Stars, expect payment spikes after big auctions, and remember that Stars paid to a bot follow the usual availability rules in the &lt;a href="https://telegram.org/tos/bot-developers" rel="noopener noreferrer"&gt;Bot Platform Developer Terms&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] treat &lt;code&gt;starGift.auction&lt;/code&gt; gifts as bid-only, never send them through the normal gift flow&lt;/li&gt;
&lt;li&gt;[ ] apply auction states by &lt;code&gt;version&lt;/code&gt;; apply &lt;code&gt;starGiftAuctionStateFinished&lt;/code&gt; unconditionally&lt;/li&gt;
&lt;li&gt;[ ] show the bid as locked until a win, a refund or &lt;code&gt;returned&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] estimate position from &lt;code&gt;bid_levels&lt;/code&gt; and label approximate positions&lt;/li&gt;
&lt;li&gt;[ ] tell users that auction gifts cannot be converted back into Stars&lt;/li&gt;
&lt;li&gt;[ ] read resale commission and limits from the app config&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Disclosure: we build &lt;a href="https://stargate.zone/about" rel="noopener noreferrer"&gt;StarGate&lt;/a&gt;, a Telegram bot and web app that swaps Stars for GRAM, USDT, ETH and BTC. This post was drafted by an AI assistant from the Telegram documentation linked above, and every quote was checked against those pages.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>telegram</category>
      <category>api</category>
      <category>bots</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Telegram gifts in the Bot API: regular vs unique, conversion to Stars, transfers and the blockchain flag</title>
      <dc:creator>StarGate</dc:creator>
      <pubDate>Thu, 29 Oct 2026 01:44:45 +0000</pubDate>
      <link>https://dev.to/stargatezone/telegram-gifts-in-the-bot-api-regular-vs-unique-conversion-to-stars-transfers-and-the-blockchain-117p</link>
      <guid>https://dev.to/stargatezone/telegram-gifts-in-the-bot-api-regular-vs-unique-conversion-to-stars-transfers-and-the-blockchain-117p</guid>
      <description>&lt;p&gt;Users keep asking bots that deal in Telegram Stars what they can do with the gifts they received. The answer lives in a handful of Bot API types and methods, and several of them have limits that are easy to miss. This post collects what the &lt;a href="https://core.telegram.org/bots/api" rel="noopener noreferrer"&gt;Bot API&lt;/a&gt; and the &lt;a href="https://core.telegram.org/api/gifts" rel="noopener noreferrer"&gt;MTProto gift docs&lt;/a&gt; actually say, so your bot can tell a user what is possible before they try.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two kinds of owned gifts
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;getUserGifts&lt;/code&gt; (any user) and &lt;code&gt;getBusinessAccountGifts&lt;/code&gt; (a business account your bot manages) return &lt;code&gt;OwnedGifts&lt;/code&gt;, a list of two types:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;OwnedGiftRegular&lt;/code&gt;&lt;/strong&gt; — an ordinary gift.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;convert_star_count&lt;/code&gt;: "Number of Telegram Stars that can be claimed by the receiver instead of the gift; omitted if the gift cannot be converted to Telegram Stars".&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;can_be_upgraded&lt;/code&gt;: the gift can become a unique one.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;prepaid_upgrade_star_count&lt;/code&gt;: the sender already paid for the upgrade.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;was_refunded&lt;/code&gt;: the gift "was refunded and isn't available anymore".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;OwnedGiftUnique&lt;/code&gt;&lt;/strong&gt; — an upgraded collectible.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;can_be_transferred&lt;/code&gt; and &lt;code&gt;transfer_star_count&lt;/code&gt;: whether the gift can move to another owner and how many Stars that costs.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;next_transfer_date&lt;/code&gt;: "Point in time (Unix timestamp) when the gift can be transferred. If it is in the past, then the gift can be transferred now."&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gift&lt;/code&gt; is a &lt;code&gt;UniqueGift&lt;/code&gt; with &lt;code&gt;number&lt;/code&gt;, &lt;code&gt;model&lt;/code&gt;, &lt;code&gt;symbol&lt;/code&gt;, &lt;code&gt;backdrop&lt;/code&gt;, &lt;code&gt;is_burned&lt;/code&gt; (used to craft another gift) and &lt;code&gt;is_from_blockchain&lt;/code&gt;: the gift was assigned from the blockchain and "can't be resold or transferred in Telegram".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Several fields are filled only "for gifts received on behalf of business accounts". With plain &lt;code&gt;getUserGifts&lt;/code&gt; you see what a user owns, not everything they can do with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conversion to Stars destroys the gift and pays less
&lt;/h2&gt;

&lt;p&gt;From the MTProto docs on &lt;code&gt;payments.convertStarGift&lt;/code&gt;: the operation "will permanently destroy the gift", and the Stars credited "will be less than the buying price". Two more limits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A gift "can be converted back into Telegram Stars only if it was received less than &lt;code&gt;stargifts_convert_period_max&lt;/code&gt; seconds ago". The client config documents it as &lt;code&gt;604800&lt;/code&gt;, seven days.&lt;/li&gt;
&lt;li&gt;"Gifts acquired through collectible gift auctions cannot be converted back into Telegram Stars."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://core.telegram.org/api/stars" rel="noopener noreferrer"&gt;Stars docs&lt;/a&gt; add a side effect users rarely expect: "if you refund Star purchases or convert gifts to Stars your rating will decrease."&lt;/p&gt;

&lt;p&gt;For a business account, the Bot API method is &lt;code&gt;convertGiftToStars(business_connection_id, owned_gift_id)&lt;/code&gt; and needs the &lt;code&gt;can_convert_gifts_to_stars&lt;/code&gt; business bot right.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gifts sent by a bot cannot be converted
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;sendGift&lt;/code&gt; documents it in one line: "The gift can't be converted to Telegram Stars by the receiver." If your bot rewards users with gifts, the reward cannot be turned into Stars. Say so in the UI, or send Stars-denominated value another way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upgrades and transfers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;upgradeGift&lt;/code&gt; turns a regular gift into a unique one. It needs &lt;code&gt;can_transfer_and_upgrade_gifts&lt;/code&gt;, plus &lt;code&gt;can_transfer_stars&lt;/code&gt; when the upgrade is paid from the business account balance. &lt;code&gt;keep_original_details&lt;/code&gt; keeps the original text, sender and receiver.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;transferGift&lt;/code&gt; sends a unique gift to another chat. The receiving chat "must be active in the last 24 hours", and a positive &lt;code&gt;star_count&lt;/code&gt; is paid from the business account balance.&lt;/li&gt;
&lt;li&gt;Before offering a transfer, compare &lt;code&gt;next_transfer_date&lt;/code&gt; with the current time. A gift that was just bought or upgraded may be locked for a while.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Stars out of a business account
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;getBusinessAccountStarBalance&lt;/code&gt; reads the balance (&lt;code&gt;can_view_gifts_and_stars&lt;/code&gt;), and &lt;code&gt;transferBusinessAccountStars&lt;/code&gt; moves 1 to 10,000 Stars to the bot's own balance (&lt;code&gt;can_transfer_stars&lt;/code&gt;). Those Stars are then ordinary bot Stars: subject to the same up-to-21-day availability rule in the &lt;a href="https://telegram.org/tos/bot-developers" rel="noopener noreferrer"&gt;Bot Platform Developer Terms&lt;/a&gt; as any Stars your bot receives.&lt;/p&gt;

&lt;h2&gt;
  
  
  A decision tree for users
&lt;/h2&gt;

&lt;p&gt;The fields above are enough to answer "what can I do with this gift?" without guessing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;OwnedGiftRegular&lt;/code&gt; with &lt;code&gt;convert_star_count&lt;/code&gt; present: converting gives exactly that many Stars, destroys the gift and lowers the Stars rating.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;OwnedGiftRegular&lt;/code&gt; without &lt;code&gt;convert_star_count&lt;/code&gt;: the conversion window closed, the gift came from a bot, or it was won at an auction.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;OwnedGiftUnique&lt;/code&gt;: sell it on Telegram's marketplace or through a buy offer; check &lt;code&gt;next_transfer_date&lt;/code&gt; before any transfer.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;is_from_blockchain&lt;/code&gt;: it lives on-chain, so it is sold there, not in Telegram.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Whatever route ends in Stars, those Stars can be paid to a bot like any other Stars.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] read &lt;code&gt;convert_star_count&lt;/code&gt;, never compute the conversion amount yourself&lt;/li&gt;
&lt;li&gt;[ ] treat a missing &lt;code&gt;convert_star_count&lt;/code&gt; as "cannot convert", not as zero&lt;/li&gt;
&lt;li&gt;[ ] warn that conversion destroys the gift and lowers the Stars rating&lt;/li&gt;
&lt;li&gt;[ ] tell users that gifts your bot sends cannot be converted&lt;/li&gt;
&lt;li&gt;[ ] check &lt;code&gt;next_transfer_date&lt;/code&gt; and &lt;code&gt;can_be_transferred&lt;/code&gt; before a transfer&lt;/li&gt;
&lt;li&gt;[ ] handle &lt;code&gt;is_from_blockchain&lt;/code&gt; and &lt;code&gt;is_burned&lt;/code&gt; gifts as not transferable in Telegram&lt;/li&gt;
&lt;li&gt;[ ] request only the business bot rights you use&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Disclosure: we build &lt;a href="https://stargate.zone/about" rel="noopener noreferrer"&gt;StarGate&lt;/a&gt;, a Telegram bot and web app that swaps Stars for GRAM, USDT, ETH and BTC. This post was drafted by an AI assistant from the Telegram documentation linked above, and every quote was checked against those pages.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>telegram</category>
      <category>bots</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Telegram Stars refunds and the 21-day window: what a bot that pays out for Stars must handle</title>
      <dc:creator>StarGate</dc:creator>
      <pubDate>Sat, 24 Oct 2026 11:50:11 +0000</pubDate>
      <link>https://dev.to/stargatezone/telegram-stars-refunds-and-the-21-day-window-what-a-bot-that-pays-out-for-stars-must-handle-3jh7</link>
      <guid>https://dev.to/stargatezone/telegram-stars-refunds-and-the-21-day-window-what-a-bot-that-pays-out-for-stars-must-handle-3jh7</guid>
      <description>&lt;p&gt;If your bot sells something that cannot be taken back (crypto, a gift card, a payout of any kind) for Telegram Stars, a refund or chargeback can leave you with neither the Stars nor the goods. This post collects what the Bot API and Telegram's terms actually say about refunds, and the pattern we use in a production bot: hold the payout through the refund window, detect refunds by polling &lt;code&gt;getStarTransactions&lt;/code&gt;, and let users post collateral to skip the hold.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Telegram says
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Stars are not available at once.&lt;/strong&gt; &lt;a href="https://telegram.org/tos/bot-developers" rel="noopener noreferrer"&gt;Bot Platform Developer Terms&lt;/a&gt;, section 6.2.4:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For technical reasons, Stars in your balance may not become available for advertising credits or rewards for up to 21 days after their receipt.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Chargebacks hit the bot's balance.&lt;/strong&gt; From the &lt;a href="https://core.telegram.org/bots/api" rel="noopener noreferrer"&gt;Bot API&lt;/a&gt; description of &lt;code&gt;StarTransaction&lt;/code&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that if the buyer initiates a chargeback with the payment provider from whom they acquired Stars (e.g., Apple, Google) following this transaction, the refunded Stars will be deducted from the bot's balance. This is outside of Telegram's control.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The developer terms add that the seller "will be responsible for the funds returned" and that Telegram "expressly discourages the real-time sale of digital products" that cannot be reclaimed after a successful dispute or chargeback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You must handle disputes.&lt;/strong&gt; &lt;a href="https://core.telegram.org/bots/payments-stars" rel="noopener noreferrer"&gt;Payments in Telegram Stars&lt;/a&gt; requires bots to answer &lt;code&gt;/paysupport&lt;/code&gt; and to be "prepared to correctly process disputes and chargebacks". A bot refunds a payment itself with &lt;code&gt;refundStarPayment(user_id, telegram_payment_charge_id)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;None of these pages gives a deadline for chargebacks. The 21 days is the availability delay, and it is the window most services use for their own hold.&lt;/p&gt;

&lt;h2&gt;
  
  
  The attack
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Buy Stars through the App Store or Google Play.&lt;/li&gt;
&lt;li&gt;Pay your bot's invoice; the bot sends crypto immediately.&lt;/li&gt;
&lt;li&gt;File a chargeback with the store. The Stars are deducted from the bot's balance.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The crypto is gone for good. This is not theoretical: it happened to us before we added a hold.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 1: hold the payout through the window
&lt;/h2&gt;

&lt;p&gt;On &lt;code&gt;successful_payment&lt;/code&gt;, store &lt;code&gt;telegram_payment_charge_id&lt;/code&gt; together with your order. If the order is above a risk threshold, give it a &lt;code&gt;hold_until = paid_at + 21 days&lt;/code&gt; and pay out from a scheduled job once that time has passed. Show the user the exact release date; a hold without a date looks like a scam.&lt;/p&gt;

&lt;p&gt;Keep the threshold and the per-user risk rules on the server. If the quote endpoint reveals exactly which users or amounts are held, it tells an attacker how to stay just under the line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 2: detect refunds by polling &lt;code&gt;getStarTransactions&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;There is no webhook for a chargeback. Refunds show up in the bot's transaction list instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;getStarTransactions(offset, limit)&lt;/code&gt; returns transactions "in chronological order", up to 100 per call. Keep the offset in persistent storage and walk forward from it.&lt;/li&gt;
&lt;li&gt;A refund is an &lt;strong&gt;outgoing&lt;/strong&gt; transaction: &lt;code&gt;receiver&lt;/code&gt; is set (the docs name "a user for a purchase refund" as an example), with &lt;code&gt;receiver.type = "user"&lt;/code&gt; and &lt;code&gt;receiver.transaction_type = "invoice_payment"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;receiver.invoice_payload&lt;/code&gt; is optional, so do not rely on it alone.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;StarTransaction.id&lt;/code&gt; "coincides with the identifier of the original transaction for refund transactions" and, for incoming payments, "coincides with SuccessfulPayment.telegram_payment_charge_id". That gives a reliable link from a refund back to your order.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Match by payload first and fall back to the charge id. If neither gives exactly one order, do not guess: a fuzzy match can punish the wrong user. On a confirmed match, mark the order forfeited, record the debt on the account and block it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 3: collateral instead of waiting
&lt;/h2&gt;

&lt;p&gt;A hold protects you, but a 21-day wait is bad for users who sell regularly. The way around it is collateral: the user deposits crypto, and while its free amount covers the refund risk of a swap, that swap pays out at once.&lt;/p&gt;

&lt;p&gt;Rules that kept it safe for us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reserve, then pay.&lt;/strong&gt; Reserve collateral for a swap after the payment is confirmed and before the payout, under a lock (we use Symfony Lock), so two parallel payouts cannot both spend the same collateral.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Freeze per swap.&lt;/strong&gt; Each reserved portion has its own unlock date, the end of that swap's refund window. One deposit can have several.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forfeit on refund.&lt;/strong&gt; The refund detector from pattern 2 forfeits exactly the collateral reserved for the refunded swap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cooldown after withdrawal.&lt;/strong&gt; A short cooldown after a collateral withdrawal stops a deposit, swap, withdraw, refund loop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cover held swaps first.&lt;/strong&gt; When new collateral arrives, release swaps that are already waiting before new ones use it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;code&gt;telegram_payment_charge_id&lt;/code&gt; stored with every order&lt;/li&gt;
&lt;li&gt;[ ] payouts above the threshold held, with the release date shown to the user&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;getStarTransactions&lt;/code&gt; polled from a persisted offset&lt;/li&gt;
&lt;li&gt;[ ] refunds matched by payload, then by charge id, with no fuzzy matching&lt;/li&gt;
&lt;li&gt;[ ] refund leads to forfeit, debt and block&lt;/li&gt;
&lt;li&gt;[ ] collateral reserved under a lock and frozen per swap&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;/paysupport&lt;/code&gt; answered&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Disclosure: we build &lt;a href="https://stargate.zone/about" rel="noopener noreferrer"&gt;StarGate&lt;/a&gt;, a Telegram bot and web app that swaps Stars for GRAM, USDT, ETH and BTC, and the patterns above come from running it. This post was drafted by an AI assistant from our code and notes, and its quotes were checked against the Telegram documentation linked above.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>telegram</category>
      <category>bots</category>
      <category>payments</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to sell Telegram Stars for crypto</title>
      <dc:creator>StarGate</dc:creator>
      <pubDate>Fri, 23 Oct 2026 13:25:23 +0000</pubDate>
      <link>https://dev.to/stargatezone/how-to-sell-telegram-stars-for-crypto-59b7</link>
      <guid>https://dev.to/stargatezone/how-to-sell-telegram-stars-for-crypto-59b7</guid>
      <description>&lt;p&gt;&lt;em&gt;Turn Telegram Stars into GRAM, USDT, ETH or BTC: the official route, the bot route, and the 21-day hold.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Short answer.&lt;/strong&gt; You can sell Telegram Stars for crypto in two ways. If you are a channel or bot owner, Telegram's official Fragment platform withdraws earned Stars to a GRAM wallet once you have at least 1,000 Stars and 21 days have passed. Anyone else can pay Stars to a swap bot such as StarGate, which pays out GRAM, USDT, ETH or BTC to a wallet you choose, from 25 Stars.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can you sell Telegram Stars at all?
&lt;/h2&gt;

&lt;p&gt;Telegram Stars are Telegram's built-in currency. You buy them in the Telegram app or through official Telegram bots and spend them in bots and mini apps.&lt;/p&gt;

&lt;p&gt;There is no button in Telegram that turns purchased Stars back into money. What you can do is spend Stars in a bot that pays you crypto in return. That is how Stars exchange bots work: you pay a normal Telegram invoice in Stars, and the bot sends crypto to your wallet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1: Fragment (channel and bot owners)
&lt;/h2&gt;

&lt;p&gt;Fragment is Telegram's own platform. It is the route for Stars you &lt;strong&gt;earned&lt;/strong&gt; as a creator: paid posts, subscriptions, bot sales.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only Stars received by your channel or bot can be withdrawn, not Stars you bought.&lt;/li&gt;
&lt;li&gt;The minimum is 1,000 Stars.&lt;/li&gt;
&lt;li&gt;Earned Stars become withdrawable 21 days after you receive them.&lt;/li&gt;
&lt;li&gt;The payout arrives in GRAM to the wallet you connect on Fragment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Option 2: a Stars swap bot (anyone with Stars)
&lt;/h2&gt;

&lt;p&gt;A swap bot accepts Stars as payment and sends crypto. With StarGate it works like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;a href="https://t.me/stargateton_bot?start=devto_sell_stars" rel="noopener noreferrer"&gt;@stargateton_bot&lt;/a&gt; or &lt;a href="https://stargate.zone/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=sell-stars" rel="noopener noreferrer"&gt;stargate.zone&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Stars&lt;/strong&gt; as what you send and the coin you want: GRAM, USDT (Jetton or ERC-20), ETH or BTC.&lt;/li&gt;
&lt;li&gt;Paste your wallet address. No wallet yet? A free wallet takes a minute to create.&lt;/li&gt;
&lt;li&gt;The app shows the exact amount you will receive before you pay. Confirm the Telegram invoice with one tap.&lt;/li&gt;
&lt;li&gt;Follow the order screen. The crypto goes to the address you gave.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One swap takes from 25 to 10,000 Stars. The rate follows the market and already includes the service fee: the amount shown before you confirm is the amount you get.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why some Stars payouts wait 21 days
&lt;/h2&gt;

&lt;p&gt;Telegram lets Stars buyers ask for a refund within 21 days. A fraudster could pay Stars, take the crypto and then refund the Stars. To protect against that, larger Stars swaps are paid out after the refund window closes. The order shows the exact release date, and the crypto arrives by itself after it.&lt;/p&gt;

&lt;p&gt;You can skip the wait with &lt;strong&gt;collateral&lt;/strong&gt;: a refundable deposit in crypto. While your collateral covers the swap, the payout is sent at once. Details: &lt;a href="https://stargate.zone/docs/hold-and-stake?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=sell-stars" rel="noopener noreferrer"&gt;The 21-day hold and collateral&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fragment or a bot: side by side
&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;Fragment&lt;/th&gt;
&lt;th&gt;Swap bot (StarGate)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Who can use it&lt;/td&gt;
&lt;td&gt;channel and bot owners&lt;/td&gt;
&lt;td&gt;anyone with Stars&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Which Stars&lt;/td&gt;
&lt;td&gt;earned Stars&lt;/td&gt;
&lt;td&gt;any Stars you can spend&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Minimum&lt;/td&gt;
&lt;td&gt;1,000 Stars&lt;/td&gt;
&lt;td&gt;25 Stars&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What you get&lt;/td&gt;
&lt;td&gt;GRAM&lt;/td&gt;
&lt;td&gt;GRAM, USDT, ETH or BTC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;When&lt;/td&gt;
&lt;td&gt;21 days after the Stars were earned&lt;/td&gt;
&lt;td&gt;at once with collateral, otherwise after the refund window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price&lt;/td&gt;
&lt;td&gt;Telegram's rate&lt;/td&gt;
&lt;td&gt;market rate, shown before you pay&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How much will I get for 1,000 Stars?
&lt;/h2&gt;

&lt;p&gt;It depends on the rate at the moment you swap: it follows the market and changes during the day. Do not trust a fixed number from an article, this one included. Open the swap form and enter the amount: it shows exactly what will arrive before you pay anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to avoid scams when selling Stars
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A real service never writes to you first. "Support" that messages you first is fake.&lt;/li&gt;
&lt;li&gt;Nobody honest asks for your seed phrase (secret words).&lt;/li&gt;
&lt;li&gt;Payouts go only to the address you entered. Nobody needs a "fee" from you to unlock a payout.&lt;/li&gt;
&lt;li&gt;Check the bot's username letter by letter. StarGate's official bot is &lt;code&gt;@stargateton_bot&lt;/code&gt;, support is &lt;code&gt;t.me/stargate_bridge&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Be careful with bots that "buy NFT gifts" for more than they are worth: many are wallet drainers.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;Can I sell Stars I bought, not earned?&lt;/strong&gt;&lt;br&gt;
Not through Fragment. Through a swap bot, yes: you spend them on the bot's invoice like on any other purchase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which coin should I choose?&lt;/strong&gt;&lt;br&gt;
GRAM if you stay in the Telegram ecosystem, USDT if you want a stable value, ETH or BTC if you already hold them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if I paid and the order closed?&lt;/strong&gt;&lt;br&gt;
Pay the invoice before the order timer runs out: an unpaid order closes by itself and nothing is charged. If you paid an invoice after the order had closed, write to support with the order number.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can someone else pay for my order?&lt;/strong&gt;&lt;br&gt;
Yes, a friend can pay the invoice. The order and its payout stay yours. If the Stars payment is later refunded through Telegram, the order is forfeited.&lt;/p&gt;

&lt;h2&gt;
  
  
  Official links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Bot: &lt;a href="https://t.me/stargateton_bot?start=devto_sell_stars" rel="noopener noreferrer"&gt;t.me/stargateton_bot&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Website: &lt;a href="https://stargate.zone/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=sell-stars" rel="noopener noreferrer"&gt;stargate.zone&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Support chat: &lt;a href="https://t.me/stargate_bridge" rel="noopener noreferrer"&gt;t.me/stargate_bridge&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Email: &lt;a href="mailto:hello@stargate.zone"&gt;hello@stargate.zone&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;How a Stars swap works, step by step: &lt;a href="https://stargate.zone/docs/stars-swaps?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=sell-stars" rel="noopener noreferrer"&gt;stargate.zone/docs/stars-swaps&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Written by the StarGate team. StarGate is a wallet, swap and bridge service for crypto and Telegram Stars. About us and contacts: &lt;a href="https://stargate.zone/about?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=sell-stars" rel="noopener noreferrer"&gt;stargate.zone/about&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://medium.com/@stargatezone/how-to-sell-telegram-stars-for-crypto-54aa961db402" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>telegram</category>
      <category>cryptocurrency</category>
      <category>blockchain</category>
      <category>web3</category>
    </item>
  </channel>
</rss>
