<?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: apiTON</title>
    <description>The latest articles on DEV Community by apiTON (@apiton).</description>
    <link>https://dev.to/apiton</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%2F2355418%2F2cb30c41-c108-4833-a8c5-74097aa2d039.png</url>
      <title>DEV Community: apiTON</title>
      <link>https://dev.to/apiton</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/apiton"/>
    <language>en</language>
    <item>
      <title>How .ton domains expire, and how to find expired ones</title>
      <dc:creator>apiTON</dc:creator>
      <pubDate>Thu, 05 Nov 2026 15:00:00 +0000</pubDate>
      <link>https://dev.to/apiton/how-ton-domains-expire-and-how-to-find-expired-ones-1kp6</link>
      <guid>https://dev.to/apiton/how-ton-domains-expire-and-how-to-find-expired-ones-1kp6</guid>
      <description>&lt;p&gt;A &lt;code&gt;.ton&lt;/code&gt; domain is not rented from a registrar. It is an NFT that lives in a smart contract on&lt;br&gt;
the TON blockchain, and its "registration period" is a timestamp stored inside that contract.&lt;br&gt;
This article explains the exact rule, what happens on expiry, and why finding expired domains is&lt;br&gt;
harder than it sounds.&lt;/p&gt;
&lt;h2&gt;
  
  
  A domain is an NFT in the root DNS collection
&lt;/h2&gt;

&lt;p&gt;Every &lt;code&gt;.ton&lt;/code&gt; name is an item of one NFT collection, the TON DNS root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;raw:          0:b774d95eb20543f186c06b371ab88ad704f7e256130caf96189368a7d0cb6ccf
bounceable:   EQC3dNlesgVD8YbAazcauIrXBPfiVhMMr5YYk2in0Mtsz0Bz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The collection follows the NFT standard (&lt;a href="https://github.com/ton-blockchain/TEPs/blob/master/text/0062-nft-standard.md" rel="noopener noreferrer"&gt;TEP-62&lt;/a&gt;)&lt;br&gt;
and the DNS standard (&lt;a href="https://github.com/ton-blockchain/TEPs/blob/master/text/0081-dns-standard.md" rel="noopener noreferrer"&gt;TEP-81&lt;/a&gt;).&lt;br&gt;
One detail matters a lot for everything below: the &lt;strong&gt;index&lt;/strong&gt; of a domain item is not a running&lt;br&gt;
counter (0, 1, 2, …) but a hash of the name. Given a name you can compute the item address&lt;br&gt;
directly (&lt;code&gt;get_nft_address_by_index&lt;/code&gt; with the hashed name), but you cannot walk the collection&lt;br&gt;
from item 0 to item N and list every domain.&lt;/p&gt;
&lt;h2&gt;
  
  
  The one-year rule
&lt;/h2&gt;

&lt;p&gt;Each domain item stores &lt;code&gt;last_fill_up_time&lt;/code&gt; — the moment the owner last "filled up" the&lt;br&gt;
domain. The domain is valid for one year after that. In apiton the expiry is computed as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;expires_at = last_fill_up_time + 31 622 400 seconds   (366 days)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can read the timestamp yourself with the get-method &lt;code&gt;get_last_fill_up_time&lt;/code&gt; on the domain's&lt;br&gt;
NFT item. Two practical notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Actions of the owner that rewrite the item — for example changing a DNS record, which is what
"renew" buttons in wallets and DNS managers do — set &lt;code&gt;last_fill_up_time&lt;/code&gt; to "now" and start a
fresh year.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_last_fill_up_time == 0&lt;/code&gt; does &lt;strong&gt;not&lt;/strong&gt; mean "expired decades ago". It means the item was
never initialised. Always check &lt;code&gt;t &amp;gt; 0&lt;/code&gt; before you compare &lt;code&gt;t + 31622400&lt;/code&gt; with the current time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On apiton.org every domain page shows the expiry date and the days left; the hub&lt;br&gt;
&lt;a href="https://apiton.org/domains/expiring/" rel="noopener noreferrer"&gt;Domains expiring soon&lt;/a&gt; lists names whose year ends within the next 30 days.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens after expiry
&lt;/h2&gt;

&lt;p&gt;Nothing happens automatically at the exact second a domain lapses — there is no on-chain "expired"&lt;br&gt;
event. The contract (&lt;a href="https://github.com/ton-blockchain/dns-contract" rel="noopener noreferrer"&gt;&lt;code&gt;nft-item.fc&lt;/code&gt; in ton-blockchain/dns-contract&lt;/a&gt;)&lt;br&gt;
simply starts accepting a special message once more than a year has passed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Anyone may send &lt;code&gt;dns_balance_release&lt;/code&gt; (op &lt;code&gt;0x4ed14b65&lt;/code&gt;) to the expired item, attaching at least
the contract's minimum price for that name (&lt;code&gt;get_min_price&lt;/code&gt;, which depends on the name's length).&lt;/li&gt;
&lt;li&gt;That sender becomes the first bidder, the previous owner loses the domain, and a new auction
starts with a duration of one week.&lt;/li&gt;
&lt;li&gt;Each next bid must be at least 5 % higher than the current one. A bid in the last hour moves
the end so that at least one hour remains.&lt;/li&gt;
&lt;li&gt;When the auction ends, the highest bidder becomes the owner, and &lt;code&gt;last_fill_up_time&lt;/code&gt; starts a new year.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Until someone sends that first message, an expired domain just sits there with its old owner&lt;br&gt;
recorded. That is why "expired" really means "claimable", not "gone".&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the op-code trick does not find expired domains
&lt;/h2&gt;

&lt;p&gt;A popular idea is to scan the chain for &lt;code&gt;dns_balance_release&lt;/code&gt; messages and treat their destinations&lt;br&gt;
as expired domains. We tested this against live data: of the 50 most recent destinations, &lt;strong&gt;none&lt;/strong&gt;&lt;br&gt;
was expired — all 50 were active, because the message itself starts the release auction that&lt;br&gt;
re-assigns the domain. The op-code tells you which domains were &lt;em&gt;just re-claimed&lt;/em&gt;, not which are&lt;br&gt;
available now.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to actually find expired domains
&lt;/h2&gt;

&lt;p&gt;There is no network-wide "list of expired domains" you can ask a liteserver for. Workable options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Check a specific name.&lt;/strong&gt; Hash the name, get the item address from the root collection, read
&lt;code&gt;get_last_fill_up_time&lt;/code&gt;. If the item does not exist, the name has never been registered; if
&lt;code&gt;t &amp;gt; 0&lt;/code&gt; and &lt;code&gt;t + 31622400 &amp;lt; now&lt;/code&gt;, it is expired. Feed a wordlist to this check and you get free
names without enumerating the chain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use an index of known domains.&lt;/strong&gt; apiton keeps a catalog of domains it has seen (from an
indexer, from liteserver reads and from every lookup made through the API) and re-checks their
expiry. The &lt;a href="https://apiton.org/domains/available/" rel="noopener noreferrer"&gt;Expired .ton domains&lt;/a&gt; hub lists the ones whose year has run out;
&lt;a href="https://apiton.org/domains/length/4/" rel="noopener noreferrer"&gt;short&lt;/a&gt; and &lt;a href="https://apiton.org/domains/numeric/" rel="noopener noreferrer"&gt;numeric&lt;/a&gt; names have their own hubs. The catalog
only covers domains it has observed, so it is a good starting list, not a complete registry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask the bot.&lt;/strong&gt; Send any &lt;code&gt;name.ton&lt;/code&gt; to &lt;a href="https://t.me/apitonBot" rel="noopener noreferrer"&gt;@apitonBot&lt;/a&gt; — it reads the item
live and shows the owner, the expiry date and any sale or auction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before you bid, re-check the domain live: catalog data is cached, and someone else may already&lt;br&gt;
have started the release auction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;.ton&lt;/code&gt; domain is valid for 366 days after &lt;code&gt;last_fill_up_time&lt;/code&gt;; the owner renews by updating the item.&lt;/li&gt;
&lt;li&gt;After expiry, anyone can start a one-week release auction with &lt;code&gt;dns_balance_release&lt;/code&gt; and a minimum bid.&lt;/li&gt;
&lt;li&gt;Domains are hash-indexed, so you check names, you do not enumerate them.&lt;/li&gt;
&lt;li&gt;Browse &lt;a href="https://apiton.org/domains/available/" rel="noopener noreferrer"&gt;expired&lt;/a&gt; and &lt;a href="https://apiton.org/domains/expiring/" rel="noopener noreferrer"&gt;expiring&lt;/a&gt; domains on apiton.org,
and check any name live in &lt;a href="https://t.me/apitonBot" rel="noopener noreferrer"&gt;@apitonBot&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ton</category>
      <category>blockchain</category>
      <category>dns</category>
      <category>web3</category>
    </item>
    <item>
      <title>How to get a webhook when a TON address changes</title>
      <dc:creator>apiTON</dc:creator>
      <pubDate>Wed, 04 Nov 2026 15:00:00 +0000</pubDate>
      <link>https://dev.to/apiton/how-to-get-a-webhook-when-a-ton-address-changes-4a2k</link>
      <guid>https://dev.to/apiton/how-to-get-a-webhook-when-a-ton-address-changes-4a2k</guid>
      <description>&lt;p&gt;Most code that follows something on TON ends up as a loop: read the address, compare it with what&lt;br&gt;
you saw last time, sleep, repeat. &lt;code&gt;v2.Watch&lt;/code&gt; in the &lt;a href="https://docs.apiton.org" rel="noopener noreferrer"&gt;apiton API&lt;/a&gt; does that&lt;br&gt;
loop for you. You name an address, and apiton stores an &lt;strong&gt;event&lt;/strong&gt; for every change and pushes it to&lt;br&gt;
your server, or keeps it for you to collect.&lt;/p&gt;

&lt;p&gt;This is the walkthrough: a key, a watch, a handler that checks the signature, and what to do if you&lt;br&gt;
have no server at all.&lt;/p&gt;
&lt;h2&gt;
  
  
  What you can watch
&lt;/h2&gt;

&lt;p&gt;A watch is one address, and the server works out what kind it is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;An NFT item&lt;/strong&gt; (a Telegram &lt;a href="https://apiton.org/usernames/" rel="noopener noreferrer"&gt;username&lt;/a&gt;, a &lt;a href="https://apiton.org/numbers/" rel="noopener noreferrer"&gt;+888 number&lt;/a&gt;, a
&lt;a href="https://apiton.org/domains/" rel="noopener noreferrer"&gt;.ton domain&lt;/a&gt;, a &lt;a href="https://apiton.org/gifts/" rel="noopener noreferrer"&gt;gift&lt;/a&gt; or any other NFT) is watched for its sale and auction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Any other address&lt;/strong&gt; is watched for its transactions, above an amount you choose.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;listed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;put on sale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;price&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the sale price changed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;delisted&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the sale or auction ended with the same owner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sold&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the sale or auction ended with a new owner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;auction_start&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;an auction began&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;a higher bid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;auction_ending&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;under 2 hours left&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tx&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;a wallet's transaction&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  Step 1: get a key
&lt;/h2&gt;

&lt;p&gt;Send &lt;code&gt;/get_api&lt;/code&gt; to &lt;a href="https://t.me/apitonBot" rel="noopener noreferrer"&gt;@apitonBot&lt;/a&gt;. The key looks like &lt;code&gt;123456789:aB3dE…&lt;/code&gt; and&lt;br&gt;
goes in a header on every call. &lt;code&gt;v2.Watch&lt;/code&gt; needs one: a watch has to belong to somebody.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 2: create a watch
&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;-X&lt;/span&gt; POST https://app.apiton.org/v2.Watch/Create &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;-H&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer 123456789:aB3dE…'&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;'{"address":"UQCdqXGvONLwOr3zCNX5FjapflorB6ZsOdcdfLrjsDLt3AF4",
       "webhook_url":"https://example.com/apiton","min_amount":1}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The answer has the watch and a &lt;code&gt;webhook_secret&lt;/code&gt; starting with &lt;code&gt;whsec_&lt;/code&gt;. &lt;strong&gt;It is shown once&lt;/strong&gt;, so&lt;br&gt;
store it now. It signs your deliveries, and it is not your API key.&lt;/p&gt;

&lt;p&gt;For a wallet, &lt;code&gt;min_amount&lt;/code&gt; is the smallest amount that raises an event, in GRAM, 1 by default. &lt;code&gt;0&lt;/code&gt;&lt;br&gt;
means every transaction. The &lt;code&gt;webhook_url&lt;/code&gt; must be HTTPS on port 443 or 8443 on a public host, and&lt;br&gt;
redirects are not followed.&lt;/p&gt;

&lt;p&gt;The watch starts from the state it finds: what happened before &lt;code&gt;Create&lt;/code&gt; raises nothing.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 3: receive the webhook
&lt;/h2&gt;

&lt;p&gt;Each event arrives as a &lt;code&gt;POST&lt;/code&gt; of JSON with three headers: &lt;code&gt;X-Apiton-Signature&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;X-Apiton-Timestamp&lt;/code&gt; and &lt;code&gt;X-Apiton-Event-Id&lt;/code&gt;. The signature is &lt;code&gt;sha256=&lt;/code&gt; plus the hex HMAC-SHA256&lt;br&gt;
of &lt;code&gt;&amp;lt;timestamp&amp;gt;.&amp;lt;body&amp;gt;&lt;/code&gt;, keyed with your secret.&lt;/p&gt;

&lt;p&gt;Check it over the &lt;strong&gt;raw body&lt;/strong&gt;, as received. Parsing the JSON and serialising it again gives&lt;br&gt;
different bytes.&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;SECRET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;APITON_WEBHOOK_SECRET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;seen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# use your database
&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;headers&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Apiton-Timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;  &lt;span class="c1"&gt;# a replayed request
&lt;/span&gt;    &lt;span class="n"&gt;want&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SECRET&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&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;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compare_digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sha256=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;want&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Apiton-Signature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/apiton&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;apiton&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_json&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;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="nf"&gt;queue_for_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# your slow work goes here, not in this request
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things in that handler are deliberate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Delivery is at least once.&lt;/strong&gt; If your 2xx gets lost, the same event comes again with the same &lt;code&gt;id&lt;/code&gt;.&lt;br&gt;
So remember the ids you have processed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Answer first, work later.&lt;/strong&gt; Only a 2xx within 5 seconds counts as delivered. A slow handler counts&lt;br&gt;
as a failure even if its work finished.&lt;/p&gt;

&lt;p&gt;To try it, call &lt;code&gt;Test&lt;/code&gt;. It sends a test event right away and tells you what your endpoint answered:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://app.apiton.org/v2.Watch/Test &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;-H&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer 123456789:aB3dE…'&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"id":"41"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  When your endpoint is down
&lt;/h2&gt;

&lt;p&gt;A failed delivery is retried after 1, 5 and 30 minutes, then every 30 minutes. After about 24 hours&lt;br&gt;
of failures the webhook is paused and nothing is pushed until a &lt;code&gt;Test&lt;/code&gt; gets through. Nothing is lost&lt;br&gt;
in the meantime: events stay readable for 7 days, as the next section shows. &lt;code&gt;List&lt;/code&gt; shows each&lt;br&gt;
webhook's state and last error.&lt;/p&gt;
&lt;h2&gt;
  
  
  No server? Use a cursor
&lt;/h2&gt;

&lt;p&gt;Leave out &lt;code&gt;webhook_url&lt;/code&gt;. Every event is stored anyway, so you can collect them from a script or a&lt;br&gt;
cron job:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://app.apiton.org/v2.Watch/Poll &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;-H&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer 123456789:aB3dE…'&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;'{"after_id":"0","limit":50}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Send &lt;code&gt;next_after_id&lt;/code&gt; as the next &lt;code&gt;after_id&lt;/code&gt;. Ids only grow, so a stored cursor never skips an event.&lt;br&gt;
Keep an eye on the cost: each &lt;code&gt;Poll&lt;/code&gt; is one request of your plan, while webhook deliveries cost none.&lt;br&gt;
A free key's 10,000 requests a month is one &lt;code&gt;Poll&lt;/code&gt; every 4½ minutes and nothing else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;Watches&lt;/th&gt;
&lt;th&gt;Each address read every&lt;/th&gt;
&lt;th&gt;Webhook pushes per hour&lt;/th&gt;
&lt;th&gt;Price for 30 days&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;free&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3 minutes&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;builder&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;30 seconds&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;5 GRAM or 1,000 Stars&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pro&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;30 seconds&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;30 GRAM or 6,000 Stars&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Events above the hourly cap are not lost: they stay in &lt;code&gt;Poll&lt;/code&gt;. Builder and pro also raise the&lt;br&gt;
general API limits; &lt;code&gt;/upgrade&lt;/code&gt; in &lt;a href="https://t.me/apitonBot" rel="noopener noreferrer"&gt;@apitonBot&lt;/a&gt; shows them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worth knowing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A status change arrives one read later.&lt;/strong&gt; &lt;code&gt;listed&lt;/code&gt;, &lt;code&gt;sold&lt;/code&gt; and the like are reported when two
reads in a row agree, so one bad read cannot fire &lt;code&gt;delisted&lt;/code&gt; and &lt;code&gt;listed&lt;/code&gt;. Prices and bids need
only one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A busy wallet.&lt;/strong&gt; One read returns up to 16 transactions. A wallet with more between two reads
gets the newest 16 and &lt;code&gt;truncated: true&lt;/code&gt;; read the rest with &lt;code&gt;Address/Transactions&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amounts are in GRAM,&lt;/strong&gt; the coin of TON, &lt;a href="https://apiton.org/blog/toncoin-is-now-gram/" rel="noopener noreferrer"&gt;formerly Toncoin&lt;/a&gt;. The API's
&lt;code&gt;symbol&lt;/code&gt; for it stays &lt;code&gt;TON&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you only want the alert in Telegram and no code, send the address to&lt;br&gt;
&lt;a href="https://t.me/apitonBot" rel="noopener noreferrer"&gt;@apitonBot&lt;/a&gt; and tap &lt;strong&gt;Watch&lt;/strong&gt;. The full reference is at&lt;br&gt;
&lt;a href="https://docs.apiton.org" rel="noopener noreferrer"&gt;docs.apiton.org&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webhooks</category>
      <category>ton</category>
      <category>blockchain</category>
      <category>api</category>
    </item>
    <item>
      <title>What anonymous +888 numbers are and how Fragment auctions work</title>
      <dc:creator>apiTON</dc:creator>
      <pubDate>Tue, 03 Nov 2026 15:00:00 +0000</pubDate>
      <link>https://dev.to/apiton/what-anonymous-888-numbers-are-and-how-fragment-auctions-work-21mi</link>
      <guid>https://dev.to/apiton/what-anonymous-888-numbers-are-and-how-fragment-auctions-work-21mi</guid>
      <description>&lt;p&gt;Telegram accounts are tied to phone numbers. &lt;strong&gt;Anonymous numbers&lt;/strong&gt; in the &lt;code&gt;+888&lt;/code&gt; range are phone&lt;br&gt;
numbers that do not belong to any mobile carrier: they exist only as NFTs on the TON blockchain and&lt;br&gt;
are sold through &lt;a href="https://fragment.com" rel="noopener noreferrer"&gt;Fragment&lt;/a&gt;. Whoever holds the NFT can use the number to sign&lt;br&gt;
up for or log in to Telegram — no SIM card involved.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you actually own
&lt;/h2&gt;

&lt;p&gt;Each &lt;code&gt;+888&lt;/code&gt; number is an item of the "Anonymous Telegram Numbers" NFT collection. Most numbers have&lt;br&gt;
eleven digits (&lt;code&gt;+888 0123 4567&lt;/code&gt;); some are shorter. Owning the item means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the number is linked to your TON wallet, and you can move it to another wallet like any NFT;&lt;/li&gt;
&lt;li&gt;you can list it for sale or put it up for auction;&lt;/li&gt;
&lt;li&gt;login codes for a Telegram account on that number are delivered through Fragment to the owner
of the NFT instead of by SMS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because the owner is a wallet address, the chain shows &lt;strong&gt;which wallet&lt;/strong&gt; holds a number, but not&lt;br&gt;
which Telegram account uses it.&lt;/p&gt;

&lt;p&gt;On apiton.org every known number has a page, for example&lt;br&gt;
&lt;a href="https://apiton.org/number/88800001312" rel="noopener noreferrer"&gt;+888 0000 1312&lt;/a&gt;, and the &lt;a href="https://apiton.org/numbers/" rel="noopener noreferrer"&gt;numbers hub&lt;/a&gt; lets you browse&lt;br&gt;
&lt;a href="https://apiton.org/numbers/for-sale/" rel="noopener noreferrer"&gt;numbers for sale&lt;/a&gt;, &lt;a href="https://apiton.org/numbers/auctions/" rel="noopener noreferrer"&gt;numbers on auction&lt;/a&gt; and series such as&lt;br&gt;
&lt;a href="https://apiton.org/numbers/series/88800/" rel="noopener noreferrer"&gt;+888 0…&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the on-chain auction works
&lt;/h2&gt;

&lt;p&gt;Fragment sells numbers and collectible usernames with the &lt;strong&gt;telemint&lt;/strong&gt; contracts published by&lt;br&gt;
Telegram (&lt;a href="https://github.com/TelegramMessenger/telemint" rel="noopener noreferrer"&gt;TelegramMessenger/telemint&lt;/a&gt;). The auction&lt;br&gt;
state is stored in the NFT item itself, so anyone can read it with get-methods&lt;br&gt;
(&lt;code&gt;get_telemint_auction_config&lt;/code&gt;, &lt;code&gt;get_telemint_auction_state&lt;/code&gt;). The config has these fields:&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;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;beneficiary&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;who receives the winning bid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;initial_min_bid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the smallest first bid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max_bid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;a "buy now" price: a bid at or above it ends the auction at once (0 = none)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;min_bid_step&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;how much larger, in percent, the next bid must be&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;min_extend_time&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;a late bid pushes the end so at least this much time remains&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;duration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the length of the auction&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;From the contract code: the next minimum bid is the larger of "current bid + 1 💎" and "current&lt;br&gt;
bid increased by &lt;code&gt;min_bid_step&lt;/code&gt; percent". When someone outbids you, your bid is sent back to your&lt;br&gt;
wallet with an "outbid" notification. When the auction ends, the NFT goes to the highest bidder&lt;br&gt;
and the beneficiary receives the funds (minus royalties, if any).&lt;/p&gt;

&lt;p&gt;The apiton API returns this as &lt;code&gt;TeleAuction&lt;/code&gt;: &lt;code&gt;last_bid&lt;/code&gt; (amount, bidder, time), &lt;code&gt;next_min_bid&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;end_time&lt;/code&gt; and the &lt;code&gt;config&lt;/code&gt; above. See &lt;a href="https://t.me/apitonBot" rel="noopener noreferrer"&gt;@apitonBot&lt;/a&gt; for the same data in&lt;br&gt;
Telegram.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixed-price listings, on-chain and off
&lt;/h2&gt;

&lt;p&gt;A number can also be offered at a fixed price:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;On-chain sale contract.&lt;/strong&gt; The owner transfers the NFT to a marketplace sale contract that
releases it to whoever pays the price. The price is readable on-chain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Off-chain Fragment listing.&lt;/strong&gt; Fragment also lets owners list a number while the NFT stays in
their wallet. There is no sale contract to read, so apiton takes the asking price from the public
Fragment page and marks it "per fragment.com".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://apiton.org/blog/where-nft-prices-come-from/" rel="noopener noreferrer"&gt;Where a price comes from&lt;/a&gt; explains the three sources in detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking a number
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open its page on apiton.org — &lt;code&gt;/number/888…&lt;/code&gt; with digits only — or send &lt;code&gt;+888 …&lt;/code&gt; to
&lt;a href="https://t.me/apitonBot" rel="noopener noreferrer"&gt;@apitonBot&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Read the status: &lt;em&gt;not listed&lt;/em&gt;, &lt;em&gt;for sale&lt;/em&gt; (with the price and its source) or &lt;em&gt;on auction&lt;/em&gt;
(current bid, next minimum bid, end time).&lt;/li&gt;
&lt;li&gt;Before paying, confirm the live state: on-chain reads are authoritative, catalog data is cached.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Numbers that do not exist as NFTs (never minted) show "not found": the &lt;code&gt;+888&lt;/code&gt; range is not fully&lt;br&gt;
issued.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;+888&lt;/code&gt; numbers are NFTs on TON; the holder can use them to log in to Telegram without a SIM.&lt;/li&gt;
&lt;li&gt;Auctions run inside the NFT contract (telemint): minimum step in percent, extension on late bids,
instant finish at &lt;code&gt;max_bid&lt;/code&gt;, automatic refunds for outbid bidders.&lt;/li&gt;
&lt;li&gt;Browse &lt;a href="https://apiton.org/numbers/for-sale/" rel="noopener noreferrer"&gt;numbers for sale&lt;/a&gt; and &lt;a href="https://apiton.org/numbers/auctions/" rel="noopener noreferrer"&gt;auctions&lt;/a&gt;, and get
alerts in &lt;a href="https://t.me/apitonBot" rel="noopener noreferrer"&gt;@apitonBot&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>telegram</category>
      <category>ton</category>
      <category>blockchain</category>
      <category>nft</category>
    </item>
    <item>
      <title>How to check who owns a Telegram username</title>
      <dc:creator>apiTON</dc:creator>
      <pubDate>Sun, 01 Nov 2026 22:14:28 +0000</pubDate>
      <link>https://dev.to/apiton/how-to-check-who-owns-a-telegram-username-1j08</link>
      <guid>https://dev.to/apiton/how-to-check-who-owns-a-telegram-username-1j08</guid>
      <description>&lt;p&gt;Since 2022 Telegram has sold short and memorable usernames on &lt;a href="https://fragment.com" rel="noopener noreferrer"&gt;Fragment&lt;/a&gt; as&lt;br&gt;
&lt;strong&gt;collectible usernames&lt;/strong&gt;. Each one is an NFT on the TON blockchain. That makes one question easy&lt;br&gt;
to answer — which wallet owns &lt;code&gt;@name&lt;/code&gt; — and leaves another, which &lt;em&gt;person&lt;/em&gt; is behind it, mostly&lt;br&gt;
unanswered. Here is what you can find out and how.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ordinary usernames&lt;/strong&gt; are set for free in Telegram settings. They are not on any blockchain,
and nobody outside Telegram can see who reserved one except by opening &lt;code&gt;t.me/&amp;lt;name&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collectible usernames&lt;/strong&gt; were auctioned on Fragment. The name is an NFT in the "Telegram
Usernames" collection, owned by a TON wallet. The owner can attach it to a Telegram account or
channel, transfer it, sell it or auction it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything below is about collectible usernames. If a lookup says "not found", the name is most&lt;br&gt;
likely an ordinary username or not taken at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: look up the NFT
&lt;/h2&gt;

&lt;p&gt;Pick one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;apiton.org&lt;/strong&gt; — open &lt;code&gt;apiton.org/username/&amp;lt;name&amp;gt;&lt;/code&gt; (lowercase, without &lt;code&gt;@&lt;/code&gt;). The page shows the
owner wallet, the status and the price if it is listed. Browse the
&lt;a href="https://apiton.org/usernames/" rel="noopener noreferrer"&gt;usernames hub&lt;/a&gt;, for example &lt;a href="https://apiton.org/usernames/length/4/" rel="noopener noreferrer"&gt;4-character usernames&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Telegram&lt;/strong&gt; — send &lt;code&gt;@name&lt;/code&gt; to &lt;a href="https://t.me/apitonBot" rel="noopener noreferrer"&gt;@apitonBot&lt;/a&gt;. The card shows the same data
read live from the chain, with buttons for the owner's transactions and alerts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API&lt;/strong&gt; — one request, no key needed for a quick check:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://app.apiton.org/v2.Nft/Username &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;'{"username":"beijing"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response contains &lt;code&gt;address&lt;/code&gt; (the NFT item), &lt;code&gt;owner_address&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt; and, for listed names,&lt;br&gt;
a &lt;code&gt;sale&lt;/code&gt; object with the price or an &lt;code&gt;auction&lt;/code&gt; object with the auction state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: read the owner field correctly
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;owner_address&lt;/code&gt; is a &lt;strong&gt;wallet&lt;/strong&gt;, shown in the non-bounceable form (&lt;code&gt;UQ…&lt;/code&gt;). See
&lt;a href="https://apiton.org/blog/ton-address-formats-raw-bounceable/" rel="noopener noreferrer"&gt;Reading a TON address&lt;/a&gt; for why the same wallet can be
written three ways.&lt;/li&gt;
&lt;li&gt;When a username is &lt;strong&gt;for sale&lt;/strong&gt; through an on-chain sale contract, the NFT technically sits in
that contract. apiton reports the &lt;strong&gt;seller&lt;/strong&gt; as the owner, not the contract, so you see the real
holder.&lt;/li&gt;
&lt;li&gt;During a &lt;strong&gt;Fragment auction&lt;/strong&gt; the NFT stays in its item contract; the current highest bidder is
a separate field (&lt;code&gt;last_bid.address&lt;/code&gt;) and becomes the owner only when the auction ends.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 3: what the chain does not tell you
&lt;/h2&gt;

&lt;p&gt;The blockchain links the username to a wallet. It does &lt;strong&gt;not&lt;/strong&gt; record which Telegram account the&lt;br&gt;
owner assigned the name to. To see the account or channel currently using the name, open&lt;br&gt;
&lt;code&gt;https://t.me/&amp;lt;name&amp;gt;&lt;/code&gt;. The account using a name and the wallet owning it can belong to different&lt;br&gt;
people — for example when a company holds names in a treasury wallet.&lt;/p&gt;

&lt;p&gt;A wallet's history can still tell you a lot: when it received the name, what else it holds and&lt;br&gt;
whether it trades actively. In &lt;a href="https://t.me/apitonBot" rel="noopener noreferrer"&gt;@apitonBot&lt;/a&gt;, tap the owner address on a&lt;br&gt;
username card to see its transactions and NFTs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: statuses you will see
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Status&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;Not listed&lt;/td&gt;
&lt;td&gt;the NFT exists and is not offered for sale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;For sale&lt;/td&gt;
&lt;td&gt;fixed price: on-chain sale contract, or an off-chain Fragment listing ("per fragment.com")&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On auction&lt;/td&gt;
&lt;td&gt;Fragment auction: current bid, next minimum bid, end time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Not found&lt;/td&gt;
&lt;td&gt;no collectible username with this name&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Lists by status: &lt;a href="https://apiton.org/usernames/for-sale/" rel="noopener noreferrer"&gt;usernames for sale&lt;/a&gt; and&lt;br&gt;
&lt;a href="https://apiton.org/usernames/auctions/" rel="noopener noreferrer"&gt;username auctions&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get notified instead of checking again
&lt;/h2&gt;

&lt;p&gt;If you are waiting for a name to be listed or its price to drop, open it in&lt;br&gt;
&lt;a href="https://t.me/apitonBot" rel="noopener noreferrer"&gt;@apitonBot&lt;/a&gt; and tap &lt;strong&gt;Watch&lt;/strong&gt;. The bot tells you when it is listed, sold,&lt;br&gt;
re-priced or when an auction gets a new bid — see&lt;br&gt;
&lt;a href="https://apiton.org/blog/apitonbot-alerts-watchlist/" rel="noopener noreferrer"&gt;Getting alerts with @apitonBot&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>telegram</category>
      <category>ton</category>
      <category>blockchain</category>
      <category>nft</category>
    </item>
    <item>
      <title>I built a TON explorer where every page is a public API call</title>
      <dc:creator>apiTON</dc:creator>
      <pubDate>Sun, 25 Oct 2026 17:11:30 +0000</pubDate>
      <link>https://dev.to/apiton/i-built-a-ton-explorer-where-every-page-is-a-public-api-call-4lcl</link>
      <guid>https://dev.to/apiton/i-built-a-ton-explorer-where-every-page-is-a-public-api-call-4lcl</guid>
      <description>&lt;p&gt;A surprising part of Telegram lives on a blockchain. Collectible &lt;strong&gt;@usernames&lt;/strong&gt;, anonymous &lt;strong&gt;+888 phone numbers&lt;/strong&gt; and &lt;strong&gt;gifts&lt;/strong&gt; are NFTs on The Open Network (TON), and so are &lt;strong&gt;.ton domains&lt;/strong&gt;. They change hands every minute — yet simple questions like &lt;em&gt;"which 4-letter usernames are on auction right now?"&lt;/em&gt; or &lt;em&gt;"which .ton domains expired this month?"&lt;/em&gt; usually mean clicking through marketplaces one item at a time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apiton.org/" rel="noopener noreferrer"&gt;apiton.org&lt;/a&gt; is my answer to that: an explorer that lists all of it with a status (not listed, for sale, on auction, expired), a price and a history, plus a directory of live TON Sites with screenshots. As of 25 October 2026 it tracks:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Kind&lt;/th&gt;
&lt;th&gt;Total&lt;/th&gt;
&lt;th&gt;For sale&lt;/th&gt;
&lt;th&gt;On auction&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Telegram usernames&lt;/td&gt;
&lt;td&gt;674,469&lt;/td&gt;
&lt;td&gt;44,460&lt;/td&gt;
&lt;td&gt;16,871&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anonymous +888 numbers&lt;/td&gt;
&lt;td&gt;136,566&lt;/td&gt;
&lt;td&gt;440&lt;/td&gt;
&lt;td&gt;84&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.ton domains&lt;/td&gt;
&lt;td&gt;199,832&lt;/td&gt;
&lt;td&gt;28,734&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;…plus 28,623 expired .ton domains, 64,596 Telegram gifts and 382 live TON Sites.&lt;/p&gt;

&lt;p&gt;But the part I want to talk about is how it is built: &lt;strong&gt;the website has no database access at all.&lt;/strong&gt; It is a separate Go program that renders every page from the same public API anyone can call. The Telegram bot &lt;a href="https://t.me/apitonBot" rel="noopener noreferrer"&gt;@apitonBot&lt;/a&gt; works the same way. When the site needs something new, it becomes an API method first.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule: the site is just another API client
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiton.org (Go, html/template) ─┐
@apitonBot (Go)                 ├──► POST https://app.apiton.org/v2.&amp;lt;Service&amp;gt;/&amp;lt;Method&amp;gt;
your script, curl, an AI agent ─┘          │
                                           ├── Postgres catalog: usernames, numbers, domains, gifts, TON Sites
                                           └── TON liteservers: live on-chain state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why bother with this discipline on a one-person project?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The API can't rot.&lt;/strong&gt; If a field breaks, my own pages break first, and I see it before anyone else does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every site feature is an API feature.&lt;/strong&gt; Expired-domain lists, the sales feed, gift collection floors, TON Site status — all of them are public methods, because the site has no other way to get them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A small blast radius.&lt;/strong&gt; The site imports no backend package. It can be cached, restarted or rewritten without touching the data layer.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Try it: no key, just curl
&lt;/h2&gt;

&lt;p&gt;The API speaks &lt;a href="https://connectrpc.com/" rel="noopener noreferrer"&gt;Connect&lt;/a&gt;, the HTTP-friendly sibling of gRPC: every call is a &lt;code&gt;POST&lt;/code&gt; with a JSON body to &lt;code&gt;https://app.apiton.org/v2.&amp;lt;Service&amp;gt;/&amp;lt;Method&amp;gt;&lt;/code&gt;. gRPC and gRPC-Web clients generated from the same protos work too. Anonymous calls are allowed — 1 request per second and 10,000 a month per IP.&lt;/p&gt;

&lt;h3&gt;
  
  
  How big is the catalog?
&lt;/h3&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 https://app.apiton.org/v2.Catalog/Summary &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="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;"kinds"&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="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"674469"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"by_status"&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="nl"&gt;"auction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"16871"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"not_listed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"613138"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"44460"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&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;"live_sites"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"382"&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;These are the numbers from the table above, and the ones on the apiton.org home page.&lt;/p&gt;

&lt;h3&gt;
  
  
  The cheapest .ton domains for sale
&lt;/h3&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 https://app.apiton.org/v2.Catalog/List &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;'{"kind":"domain","statuses":["sale"],"sort":"price_asc","limit":2}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;"items"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"domain"&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;"prommeteeev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"nft_address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UQBt9bwjG5fTpT166sh76zjM0zKQ5rvhr44_vQcE0vPK8SyN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"owner_address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UQBixgxoacPjMpiqOjr3t7FDnFsYP_Pbxg8VgK2mxckIQspb"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sale_contract"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UQCfowehxX68MXv1HGmVMdrBt2HNQdyxIWI7M41HpKiQYiuA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sale"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.468&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"price_source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chain"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"expires_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2027-10-19T16:45:46Z"&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_len"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11&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="err"&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;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"28734"&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;&lt;code&gt;price&lt;/code&gt; is in the network's own coin (shown as 💎 on the site), and &lt;code&gt;price_source&lt;/code&gt; says whether it came from a sale contract on chain or from a Fragment listing. &lt;code&gt;List&lt;/code&gt; filters by &lt;code&gt;kind&lt;/code&gt; (&lt;code&gt;username&lt;/code&gt;, &lt;code&gt;number&lt;/code&gt;, &lt;code&gt;domain&lt;/code&gt;, &lt;code&gt;gift&lt;/code&gt;), &lt;code&gt;statuses&lt;/code&gt;, &lt;code&gt;name_len&lt;/code&gt;, &lt;code&gt;digits_only&lt;/code&gt;, &lt;code&gt;prefix&lt;/code&gt; and an expiry window, and sorts by &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;price_asc&lt;/code&gt;, &lt;code&gt;price_desc&lt;/code&gt;, &lt;code&gt;expires_asc&lt;/code&gt; or &lt;code&gt;changed&lt;/code&gt;. Two more recipes:&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="c"&gt;# 4-letter usernames on auction&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://app.apiton.org/v2.Catalog/List &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;'{"kind":"username","statuses":["auction"],"name_len":4}'&lt;/span&gt;

&lt;span class="c"&gt;# expired .ton domains, most recently changed first&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://app.apiton.org/v2.Catalog/List &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;'{"kind":"domain","statuses":["expired"],"sort":"changed"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full list of fields is in &lt;a href="https://apiton.org/blog/apiton-api-catalog-list-curl/" rel="noopener noreferrer"&gt;this guide on the apiton blog&lt;/a&gt; and in the &lt;a href="https://docs.apiton.org" rel="noopener noreferrer"&gt;API reference&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What just sold
&lt;/h3&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 https://app.apiton.org/v2.Catalog/Events &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;'{"types":["sold"],"limit":3}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pass a &lt;code&gt;kind&lt;/code&gt; and a &lt;code&gt;name&lt;/code&gt; instead and you get the history of one item: listed, delisted, re-priced, sold, bids, expiry, renewal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Live state of one item
&lt;/h3&gt;

&lt;p&gt;The catalog is refreshed in the background. For the state of a single item right now, the &lt;code&gt;v2.Nft&lt;/code&gt; methods read the chain:&lt;br&gt;
&lt;/p&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 https://app.apiton.org/v2.Nft/Domain &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;'{"domain":"apiton.ton"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UQABUZhK7peJbJzDxChcbitG9oJUgxWmk1TqZwU76aMEKALt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"owner_address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UQAyJ9vVRMGVKg9-6R-KO3alF2ITcccDBAhAi1Qj4HCpVP5Q"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"time_expire"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2027-10-08T16:16:31Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"site"&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;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"adnl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"live"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"apiton — TON explorer: names, gifts and TON Sites"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"uptime_30d"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"live_since"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-07T16:18:19.961661Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"gateway_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://site.apiton.org/apiton.ton/"&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;Yes, the explorer is a TON Site itself — more on that below. &lt;code&gt;v2.Nft/Username&lt;/code&gt;, &lt;code&gt;Number&lt;/code&gt; and &lt;code&gt;Gift&lt;/code&gt; work the same way, and &lt;code&gt;v2.Address&lt;/code&gt; covers wallets: balance, jettons, NFTs and transactions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three JSON gotchas
&lt;/h2&gt;

&lt;p&gt;The responses are proto3 JSON, which surprises people coming from hand-written REST APIs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;int64 arrives as a string&lt;/strong&gt;: &lt;code&gt;"total": "28734"&lt;/code&gt;. Parse it; don't compare it to a number.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero values are omitted.&lt;/strong&gt; An empty list is &lt;code&gt;{}&lt;/code&gt;, not &lt;code&gt;{"items": [], "total": "0"}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Errors use the Connect envelope&lt;/strong&gt; with a proper HTTP status:
&lt;/li&gt;
&lt;/ul&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 https://app.apiton.org/v2.Catalog/List &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="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"kind":"planet"}'&lt;/span&gt;
&lt;span class="c"&gt;# HTTP 400&lt;/span&gt;
&lt;span class="c"&gt;# {"code":"invalid_argument","message":"unknown kind \"planet\" (username | number | domain | gift)"}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Too many requests is &lt;code&gt;resource_exhausted&lt;/code&gt; (HTTP 429). &lt;code&gt;unavailable&lt;/code&gt; means the TON liteservers are lagging; retry after a second or two. Every response carries &lt;code&gt;X-RateLimit-Limit&lt;/code&gt;, &lt;code&gt;X-RateLimit-Remaining&lt;/code&gt; and &lt;code&gt;X-RateLimit-Reset&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What public liteservers taught me
&lt;/h2&gt;

&lt;p&gt;Live reads go to the public TON liteservers. They are free, and they are moody. Between 23 and 25 October, &lt;strong&gt;54–83 % of my liteserver requests were answered with error 651&lt;/strong&gt; — "the node is behind". That rate did not change when I tripled my own background load, so it was not me.&lt;/p&gt;

&lt;p&gt;What made the API usable anyway:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A pool with failover.&lt;/strong&gt; Keep four connections open; a 429 or a 651 is retried on another server, not on the same one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't read the newest block.&lt;/strong&gt; Reads run on a masterchain block about 10 seconds behind the head, which a node that is slightly behind can still serve. If six servers refuse, the answer comes from an older block (at most 45 seconds old). The one exception: before a send, a wallet's own state is read on the newest block only, or the send fails before anything is signed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One chain read per question.&lt;/strong&gt; A 45-second live cache sits in front of the item methods. Identical concurrent requests share a single chain read that is detached from the caller's context, so a client hanging up doesn't cancel it for everyone else. Errors are never cached.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The pool's answer is final.&lt;/strong&gt; No retry loops stacked on top of it — that is how a bad minute becomes a retry storm. Clients get a clear &lt;code&gt;unavailable&lt;/code&gt; instead, and the bot retries once after 1.5 seconds.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The other lesson was quieter and more expensive. For months, every sale and auction price read silently returned nothing, because of a typo in the default URL of the &lt;em&gt;archive&lt;/em&gt; endpoint those reads went through. "No price" looked like a valid answer, so nothing alerted. The fix was a rule rather than a patch: state and get-method reads go to liteservers only, and the archive is used for old transactions only.&lt;/p&gt;

&lt;h2&gt;
  
  
  .ton domains can't be listed from the chain
&lt;/h2&gt;

&lt;p&gt;You'd think the 199,832 domains came from iterating the DNS collection. They can't. The root .ton collection indexes each item by &lt;code&gt;hash(name)&lt;/code&gt; rather than a counter, so there is no &lt;code&gt;0..N&lt;/code&gt; to walk. A domain also lapses silently: it is valid for a year after its last renewal (&lt;code&gt;last_fill_up_time&lt;/code&gt;), and no transaction marks the moment it expires.&lt;/p&gt;

&lt;p&gt;So the catalog learns about domains as they show up on chain — transfers, sales, DNS changes — and then re-reads each one on a schedule to compute its expiry. A small trap on the way: &lt;code&gt;get_last_fill_up_time&lt;/code&gt; returning &lt;code&gt;0&lt;/code&gt; means the item was never initialized, not that it expired long ago.&lt;/p&gt;

&lt;h2&gt;
  
  
  TON Sites: a directory of the "other" web
&lt;/h2&gt;

&lt;p&gt;A .ton domain can point to a website through its DNS &lt;code&gt;site&lt;/code&gt; record — an ADNL address or a TON Storage bag. apiton reads that record for every domain, probes each site over RLDP-HTTP or TON Storage, tells parked pages from live ones, ranks them and takes screenshots with a headless Chrome whose only way out is a &lt;code&gt;.ton&lt;/code&gt;-only proxy. The result is &lt;a href="https://apiton.org/sites/" rel="noopener noreferrer"&gt;apiton.org/sites/&lt;/a&gt;: 382 live sites today. Sites with clearly illegal content are listed by name with a reason, and never opened or previewed.&lt;/p&gt;

&lt;p&gt;apiton.org is one of them. &lt;code&gt;apiton.ton&lt;/code&gt; is served by a small Go program that speaks ADNL and RLDP-HTTP and reverse-proxies the regular website, &lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;HEAD&lt;/code&gt; only. Without a TON proxy, you can open it through the gateway: &lt;a href="https://site.apiton.org/apiton.ton/" rel="noopener noreferrer"&gt;site.apiton.org/apiton.ton/&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bot and the limits
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://t.me/apitonBot" rel="noopener noreferrer"&gt;@apitonBot&lt;/a&gt; is the second client of the same API. Send it a wallet address, an @username, a +888 number, a &lt;code&gt;name.ton&lt;/code&gt; or a gift link and you get a card. You can also watch items and wallets (listed, sold, new bid, incoming transaction), set price alerts, see a wallet's portfolio, and get an API key with &lt;code&gt;/get_api&lt;/code&gt;. It answers in Russian for CIS languages and in English for everyone else.&lt;/p&gt;

&lt;p&gt;With a key (&lt;code&gt;Authorization: Bearer &amp;lt;tg_id&amp;gt;:&amp;lt;secret&amp;gt;&lt;/code&gt;) you get your own quota instead of sharing one with your IP:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;free&lt;/strong&gt; — 1 request/s, 10,000 a month;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;builder&lt;/strong&gt; — 10 requests/s, 300,000 a month;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pro&lt;/strong&gt; — 100 requests/s, 3,000,000 a month.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Upgrades are paid in the bot with Telegram Stars or TON. The key, together with a wallet token, also unlocks custodial wallets, sends and jetton/NFT minting.&lt;/p&gt;

&lt;h2&gt;
  
  
  For AI assistants: llms.txt
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apiton.org/llms.txt" rel="noopener noreferrer"&gt;apiton.org/llms.txt&lt;/a&gt; is a Markdown map of the site, the API and the bot, written for LLMs. Point an assistant or an agent at it and it can answer "what's the cheapest 5-letter username on sale?" with a real API call instead of a guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack, briefly
&lt;/h2&gt;

&lt;p&gt;Go everywhere: &lt;a href="https://github.com/connectrpc/connect-go" rel="noopener noreferrer"&gt;connect-go&lt;/a&gt; over h2c for the API, Postgres through Bun, Redis for caches and quotas, &lt;a href="https://github.com/tonkeeper/tongo" rel="noopener noreferrer"&gt;tongo&lt;/a&gt; for TON. The site is server-rendered &lt;code&gt;html/template&lt;/code&gt; with no JavaScript framework. React is used only for the bot's Mini App.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Explorer: &lt;a href="https://apiton.org/" rel="noopener noreferrer"&gt;apiton.org&lt;/a&gt; — &lt;a href="https://apiton.org/usernames/" rel="noopener noreferrer"&gt;usernames&lt;/a&gt;, &lt;a href="https://apiton.org/numbers/" rel="noopener noreferrer"&gt;+888 numbers&lt;/a&gt;, &lt;a href="https://apiton.org/domains/" rel="noopener noreferrer"&gt;.ton domains&lt;/a&gt;, &lt;a href="https://apiton.org/gifts/" rel="noopener noreferrer"&gt;gifts&lt;/a&gt;, &lt;a href="https://apiton.org/sites/" rel="noopener noreferrer"&gt;TON Sites&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;API reference: &lt;a href="https://docs.apiton.org" rel="noopener noreferrer"&gt;docs.apiton.org&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Bot: &lt;a href="https://t.me/apitonBot" rel="noopener noreferrer"&gt;@apitonBot&lt;/a&gt; · news: &lt;a href="https://t.me/apiton" rel="noopener noreferrer"&gt;@apiton&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What would you build on top of it, and which method is missing? Tell me in the comments, or send &lt;code&gt;/feedback&lt;/code&gt; to the bot.&lt;/p&gt;

</description>
      <category>telegram</category>
      <category>blockchain</category>
      <category>go</category>
      <category>api</category>
    </item>
    <item>
      <title>How to Display NFT Gift Animations in Telegram</title>
      <dc:creator>apiTON</dc:creator>
      <pubDate>Wed, 29 Jan 2025 14:33:34 +0000</pubDate>
      <link>https://dev.to/apiton/how-to-display-nft-gift-animations-in-telegram-1p7i</link>
      <guid>https://dev.to/apiton/how-to-display-nft-gift-animations-in-telegram-1p7i</guid>
      <description>&lt;h1&gt;
  
  
  How to Display NFT Gift Animations in Telegram
&lt;/h1&gt;

&lt;p&gt;The introduction of NFT gifts in Telegram has raised numerous questions among developers, particularly regarding integration with the &lt;strong&gt;TON (The Open Network)&lt;/strong&gt; blockchain. In this article, we’ll explore the technical aspects of working with animated NFTs and demonstrate how to implement their display in your projects.&lt;/p&gt;

&lt;p&gt;I am the developer of the &lt;a href="https://t.me/apitonBot" rel="noopener noreferrer"&gt;TON Blockchain Explorer&lt;/a&gt;, integrated into Telegram, and will guide you through one key question:&lt;br&gt;&lt;br&gt;
&lt;strong&gt;How to display NFT animations in your own projects.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let’s examine the following NFT contract:&lt;br&gt;&lt;br&gt;
&lt;a href="https://t.me/apitonBot?start=RVFBNlQxZXQ3WVctajMwV1VianAxM1NXWWdGT0NQYTgwNi12SVhYSU1ZaW4yaEcw" rel="noopener noreferrer"&gt;0:3a4f57aded85be8f7d1651b8e9d7749662014e08f6bcd3afaf2175c83188a7da&lt;/a&gt;&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.amazonaws.com%2Fuploads%2Farticles%2Fitmxpi5k79d694xdg8g9.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.amazonaws.com%2Fuploads%2Farticles%2Fitmxpi5k79d694xdg8g9.png" alt="NFT Contract Data Example" width="648" height="649"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From this contract, we can extract data such as attributes, descriptions, and other metadata:&lt;br&gt;&lt;br&gt;
&lt;code&gt;https://nft.fragment.com/gift/hexpot-10348.json&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;By clicking the &lt;strong&gt;More&lt;/strong&gt; button, you can retrieve all available information directly:&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.amazonaws.com%2Fuploads%2Farticles%2Fx94gz22e3d2kzdic48o2.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.amazonaws.com%2Fuploads%2Farticles%2Fx94gz22e3d2kzdic48o2.png" alt="Metadata Interface" width="626" height="783"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, you’ll find a URL with a key and filename:&lt;br&gt;&lt;br&gt;
&lt;code&gt;https://nft.fragment.com/gift/hexpot-10348.lottie.json&lt;/code&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Lottie Format Basics
&lt;/h2&gt;

&lt;p&gt;The animation uses the &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Lottie_(file_format)" rel="noopener noreferrer"&gt;Lottie format&lt;/a&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Lottie&lt;/strong&gt; is a library developed by Airbnb that renders animations in JSON format, created using Adobe After Effects. It allows developers to easily integrate high-quality animations into mobile and web apps while maintaining smooth performance and scalability.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Web Implementation Example:
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://jsfiddle.net/w5dsp16v/" rel="noopener noreferrer"&gt;https://jsfiddle.net/w5dsp16v/&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdnjs.cloudflare.com/ajax/libs/lottie-web/5.12.2/lottie.min.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"animation-container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;animation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lottie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadAnimation&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;animation-container&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="na"&gt;path&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://nft.fragment.com/gift/hexpot-10348.lottie.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.amazonaws.com%2Fuploads%2Farticles%2Fq4i642rl4nwnhq6oncxq.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.amazonaws.com%2Fuploads%2Farticles%2Fq4i642rl4nwnhq6oncxq.png" alt="lottie.min.js example" width="630" height="681"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Telegram-Specific Solution
&lt;/h2&gt;

&lt;p&gt;Telegram uses the &lt;strong&gt;.tgs format&lt;/strong&gt; for stickers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;.tgs&lt;/code&gt; files are gzipped Lottie JSON files, playable via the &lt;a href="https://github.com/Samsung/rlottie" rel="noopener noreferrer"&gt;rlottie&lt;/a&gt; library.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Implementation Workflow:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Download Lottie JSON&lt;/li&gt;
&lt;li&gt;Compress with GZIP&lt;/li&gt;
&lt;li&gt;Send as Telegram document&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Go Code Example:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"bytes"&lt;/span&gt;
    &lt;span class="s"&gt;"compress/gzip"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/aaapi-net/hog"&lt;/span&gt;
    &lt;span class="n"&gt;tele&lt;/span&gt; &lt;span class="s"&gt;"gopkg.in/telebot.v3"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;getTgsAnimation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lottieUrl&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;hog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lottieUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AsBytes&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;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;gz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;compress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&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;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&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;gz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;compress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&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;var&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Buffer&lt;/span&gt;

    &lt;span class="n"&gt;writer&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;gzip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&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;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&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;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&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;buf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;sendLottieAnimation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="n"&gt;tele&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lottieUrl&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;tgsAnimation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;getTgsAnimation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lottieUrl&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;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;tele&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FromReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tgsAnimation&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;photo&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;tele&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;                 &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;MIME&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;                 &lt;span class="s"&gt;"application/x-tgsticker"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;FileName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;             &lt;span class="s"&gt;"hexpot-10348.tgs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;DisableTypeDetection&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&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="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;photo&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;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Use native &lt;code&gt;.tgs&lt;/code&gt; format for Telegram compatibility&lt;/li&gt;
&lt;li&gt;Avoid MP4/GIF conversions to preserve quality&lt;/li&gt;
&lt;li&gt;Leverage Telegram's built-in Lottie support&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Feel free to contact about TON Blockchain development &lt;a href="https://t.me/apitonDev" rel="noopener noreferrer"&gt;@apitonDev&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Share your implementations below!&lt;/p&gt;

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