<?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: Chang-Hai</title>
    <description>The latest articles on DEV Community by Chang-Hai (@changhai).</description>
    <link>https://dev.to/changhai</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%2F1491742%2F7c2395f0-c591-4139-83f9-3bc03fa36283.jpeg</url>
      <title>DEV Community: Chang-Hai</title>
      <link>https://dev.to/changhai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/changhai"/>
    <language>en</language>
    <item>
      <title>PDF Accessibility Pre-Checks in the Browser: What Automation Can (and Cannot) Tell You</title>
      <dc:creator>Chang-Hai</dc:creator>
      <pubDate>Wed, 19 Aug 2026 13:03:48 +0000</pubDate>
      <link>https://dev.to/changhai/pdf-accessibility-pre-checks-in-the-browser-what-automation-can-and-cannot-tell-you-3d1p</link>
      <guid>https://dev.to/changhai/pdf-accessibility-pre-checks-in-the-browser-what-automation-can-and-cannot-tell-you-3d1p</guid>
      <description>&lt;p&gt;A PDF can look perfectly readable on screen and still be difficult to navigate with a screen reader. The reverse is also true: a file can pass several automated checks while still containing confusing reading order, weak alternative text, or an inaccessible complex table.&lt;/p&gt;

&lt;p&gt;That is why I treat automated PDF checking as a &lt;strong&gt;pre-check&lt;/strong&gt;, not as certification.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a browser-based pre-check can inspect
&lt;/h2&gt;

&lt;p&gt;A local checker can examine machine-verifiable signals exposed by the PDF structure, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;whether the document contains tags and a usable structure tree;&lt;/li&gt;
&lt;li&gt;whether basic metadata such as a title and document language is present;&lt;/li&gt;
&lt;li&gt;whether form fields and links expose detectable structure;&lt;/li&gt;
&lt;li&gt;whether pages appear to be image-only and may need OCR;&lt;/li&gt;
&lt;li&gt;whether the file can be parsed successfully before deeper review.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These checks are useful because they turn a vague question — “is this PDF accessible?” — into a concrete list of issues to investigate.&lt;/p&gt;

&lt;h2&gt;
  
  
  What automation cannot prove
&lt;/h2&gt;

&lt;p&gt;Passing structural checks does &lt;strong&gt;not&lt;/strong&gt; prove conformance with WCAG, PDF/UA, Section 508, or any legal requirement.&lt;/p&gt;

&lt;p&gt;Several important questions still require human judgment:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Does the reading order match the intended meaning?&lt;/li&gt;
&lt;li&gt;Is alternative text accurate and useful in context?&lt;/li&gt;
&lt;li&gt;Are headings, lists, and tables tagged semantically rather than merely present?&lt;/li&gt;
&lt;li&gt;Is color contrast sufficient?&lt;/li&gt;
&lt;li&gt;Can a keyboard and real assistive technology complete the document's tasks?&lt;/li&gt;
&lt;li&gt;Are complex tables understandable when read linearly?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For formal review, the automated result should be followed by manual inspection in Adobe Acrobat or another professional PDF accessibility tool, plus assistive-technology testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why local processing matters
&lt;/h2&gt;

&lt;p&gt;PDFs often contain private material: contracts, student records, internal reports, or unpublished research. A useful first-pass checker does not need to upload that content.&lt;/p&gt;

&lt;p&gt;I maintain &lt;strong&gt;PDF Accessibility Checker&lt;/strong&gt;, a free browser-based pre-check that uses PDF.js locally. The PDF file, filename, document content, hash, and local path are not uploaded by the scanning workflow.&lt;/p&gt;

&lt;p&gt;You can try the browser-local pre-check here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pdfaccesschecker.com/?utm_source=devto&amp;amp;utm_medium=editorial_article&amp;amp;utm_campaign=backlink_batch_20260819_015&amp;amp;utm_content=browser_pdf_precheck" rel="noopener noreferrer"&gt;PDF Accessibility Checker&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Disclosure: I am the product maintainer/operator. The tool checks a limited set of machine-verifiable signals. It is not a compliance certificate and it does not replace a manual audit.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical review sequence
&lt;/h2&gt;

&lt;p&gt;A defensible workflow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run a structural pre-check.&lt;/li&gt;
&lt;li&gt;Fix obvious metadata, tagging, and OCR problems.&lt;/li&gt;
&lt;li&gt;Inspect reading order, headings, lists, links, and tables manually.&lt;/li&gt;
&lt;li&gt;Review alternative text for meaning, not just presence.&lt;/li&gt;
&lt;li&gt;Test keyboard interaction and at least one screen reader.&lt;/li&gt;
&lt;li&gt;Record which checks were automated and which were reviewed by a person.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This keeps automation in the role where it is strongest: finding repeatable structural signals quickly, while leaving semantic and experiential decisions to human reviewers.&lt;/p&gt;

&lt;p&gt;For the standards background, refer to the &lt;a href="https://www.w3.org/WAI/standards-guidelines/wcag/" rel="noopener noreferrer"&gt;W3C WCAG overview&lt;/a&gt; and the &lt;a href="https://pdfa.org/resource/tagged-pdf-best-practice-guide-syntax/" rel="noopener noreferrer"&gt;PDF Association's accessibility resources&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The most useful result from an automated checker is not a green badge. It is a clear, explainable list of what to verify next.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>privacy</category>
      <category>showdev</category>
      <category>resources</category>
    </item>
    <item>
      <title>Replacing a Next.js Microsite with an Embedded Go + Vite Binary Without Losing SEO</title>
      <dc:creator>Chang-Hai</dc:creator>
      <pubDate>Mon, 10 Aug 2026 15:00:14 +0000</pubDate>
      <link>https://dev.to/changhai/replacing-a-nextjs-microsite-with-an-embedded-go-vite-binary-without-losing-seo-41g7</link>
      <guid>https://dev.to/changhai/replacing-a-nextjs-microsite-with-an-embedded-go-vite-binary-without-losing-seo-41g7</guid>
      <description>&lt;p&gt;I recently moved a small interactive content site from a Next.js runtime to a single Go service. The motivation was operational simplicity: the product needed a polished React interface and indexable content pages, but it did not need a Node server in production.&lt;/p&gt;

&lt;p&gt;The result uses Go’s standard &lt;code&gt;net/http&lt;/code&gt; stack, an embedded Vite build, SQLite infrastructure, and build-time prerendering. This post covers the boundaries that made the migration manageable and the tradeoffs that remain.&lt;/p&gt;

&lt;h2&gt;
  
  
  The product boundary came first
&lt;/h2&gt;

&lt;p&gt;The site is a deterministic planner. Users choose a few options, compare two generated sequences, edit them, download a PNG, or share a URL. The calculation can happen entirely in the browser.&lt;/p&gt;

&lt;p&gt;That meant I could separate the system into two clear parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React owns the interactive product state and rendering.&lt;/li&gt;
&lt;li&gt;Go owns HTTP delivery, configuration, security headers, small API endpoints, and production integration points.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There was no reason to turn every user interaction into a server round trip. Recent planner choices stay in &lt;code&gt;localStorage&lt;/code&gt;, while a validated query string carries the state needed for a share link.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build React once, embed the output
&lt;/h2&gt;

&lt;p&gt;The frontend is built with React 19 and Vite. Production does not run Vite. Instead, Go embeds the finished assets:&lt;br&gt;
&lt;/p&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;webassets&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"embed"&lt;/span&gt;

&lt;span class="c"&gt;//go:embed dist/*&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;Dist&lt;/span&gt; &lt;span class="n"&gt;embed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The HTTP server opens the embedded subdirectory and serves hashed assets with long cache lifetimes. HTML uses a shorter policy so metadata and content changes can ship predictably.&lt;/p&gt;

&lt;p&gt;This keeps the deployment artifact small in concept: one binary, its environment file, CA certificates, timezone data, and a writable data directory. Node is still part of the build toolchain, but not the production runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preserve route-level HTML
&lt;/h2&gt;

&lt;p&gt;A client-rendered shell would have weakened the content pages, so the build produces complete HTML for every indexable route. Each route gets its own title, description, canonical URL, Open Graph fields, and page body.&lt;/p&gt;

&lt;p&gt;The prerender list includes the landing page, generator, style guides, eye-shape guides, methodology and safety pages, and printable template guide. A build should fail if a new indexable route is added without metadata or prerender output.&lt;/p&gt;

&lt;p&gt;The Go server substitutes the configured production origin into canonical fields. That avoids baking a staging hostname into a release while keeping generated HTML deterministic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the interactive state in one module
&lt;/h2&gt;

&lt;p&gt;The generator’s state needed to survive three paths:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;defaults created by the current product version;&lt;/li&gt;
&lt;li&gt;a saved local state from the same browser;&lt;/li&gt;
&lt;li&gt;a shared URL received from another person.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I use one normalization layer for all three. Unknown values are rejected, lengths are constrained to the supported range, cluster counts are bounded, and a custom map must match the normalized count and available lengths.&lt;/p&gt;

&lt;p&gt;The same module creates the outbound query string. A round-trip test verifies that a state encoded for sharing restores to the same normalized map.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generatorSearchParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;activeMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;selectedPlan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;restored&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generatorStateFromSearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;defaults&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deepEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;restored&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customMaps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;selectedPlan&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;activeMap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That test matters more than the framework choice. Without it, a share button can appear to work while silently changing a recipient’s map.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generate downloadable images in the browser
&lt;/h2&gt;

&lt;p&gt;The download feature draws the current map to a &lt;code&gt;canvas&lt;/code&gt; and exports a 1200×675 PNG. Native sharing uses the Web Share API when the browser supports files, then falls back to sharing or copying the URL.&lt;/p&gt;

&lt;p&gt;This is another task that did not require a backend rendering service. Keeping it local reduces infrastructure and avoids uploading a user’s work merely to create an image.&lt;/p&gt;

&lt;h2&gt;
  
  
  SQLite is infrastructure, not an excuse to collect data
&lt;/h2&gt;

&lt;p&gt;The Go template includes SQLite in WAL mode for product features that may need persistence later. The current planner does not write map data to it. Having a database available does not mean every piece of browser state belongs there.&lt;/p&gt;

&lt;p&gt;The same principle applies to authentication adapters. The template has an optional Google sign-in boundary, but the public planner does not require an account. Disabled infrastructure stays disabled until a feature has a real need, a tested flow, and matching privacy documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consent-aware analytics
&lt;/h2&gt;

&lt;p&gt;Analytics loads only after the visitor chooses to allow it. Before consent, the site keeps only bounded attribution values for the current tab. Product events describe actions such as the first interaction, download, print, or share; they do not include exact map sequences, email addresses, or photos.&lt;/p&gt;

&lt;p&gt;This required more work than dropping a script into the document head, but it made the public privacy explanation match the code path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment and rollback
&lt;/h2&gt;

&lt;p&gt;The production process is intentionally boring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;build the Vite frontend;&lt;/li&gt;
&lt;li&gt;prerender indexable routes;&lt;/li&gt;
&lt;li&gt;run frontend tests;&lt;/li&gt;
&lt;li&gt;run Go tests, race checks, and vet;&lt;/li&gt;
&lt;li&gt;compile one Linux binary;&lt;/li&gt;
&lt;li&gt;deploy it behind the existing TLS reverse proxy and systemd service.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Releases use an explicit environment value for the public base URL. The previous binary remains available for rollback. SQLite data lives outside the release directory even though the current product does not write planner state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tradeoffs
&lt;/h2&gt;

&lt;p&gt;This architecture is not a universal replacement for Next.js. It works here because the interactive state is local, the content routes are known at build time, and the server-side needs are small.&lt;/p&gt;

&lt;p&gt;It would be a worse fit if the product depended on per-request React server rendering, a large ecosystem of server components, or many dynamic pages that must be rendered with authenticated data.&lt;/p&gt;

&lt;p&gt;The migration also creates responsibility. We own the prerender pipeline, route manifest, caching behavior, and security headers. Framework defaults no longer hide mistakes, so tests must enforce those contracts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The useful lesson
&lt;/h2&gt;

&lt;p&gt;The biggest memory saving was conceptual before it was technical: decide which data truly needs a server. Once the planner, image export, and share-state encoding were treated as browser responsibilities, the production server became a compact delivery and integration layer.&lt;/p&gt;

&lt;p&gt;The live result is &lt;a href="https://lashmaplab.com/" rel="noopener noreferrer"&gt;Lash Map Lab&lt;/a&gt;, a free lash-map planner. The subject is niche, but the architecture applies to many small interactive SEO sites: prerender the known content, keep deterministic tools local, and deploy only the runtime the product actually needs.&lt;/p&gt;

&lt;p&gt;Disclosure: I’m Julian Tao, Owner of Lash Map Lab. This article describes my own product. It was prepared with AI-assisted editing and reviewed for technical accuracy before publication.&lt;/p&gt;

</description>
      <category>go</category>
      <category>react</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>How to listen for USDT (TRC20) payments in Node.js without running a full node</title>
      <dc:creator>Chang-Hai</dc:creator>
      <pubDate>Wed, 19 Nov 2025 15:39:27 +0000</pubDate>
      <link>https://dev.to/changhai/how-to-listen-for-usdt-trc20-payments-in-nodejs-without-running-a-full-node-4g36</link>
      <guid>https://dev.to/changhai/how-to-listen-for-usdt-trc20-payments-in-nodejs-without-running-a-full-node-4g36</guid>
      <description>&lt;p&gt;Building a Telegram bot is fun until you hit the "payments" part.&lt;/p&gt;

&lt;p&gt;I recently built a bot that sells digital products, and I wanted to accept USDT on Tron (TRC20) because the fees are low and everyone uses it.&lt;/p&gt;

&lt;p&gt;I thought: "How hard can it be? I just need to check if an address received money."&lt;/p&gt;

&lt;p&gt;Turns out, it's a headache.&lt;/p&gt;

&lt;p&gt;If you are a solo dev like me, you usually have two bad options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use BitPay/Coinbase Commerce&lt;/strong&gt;: Requires KYC, business docs, and they hold your funds. (No go for me).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Self-host&lt;/strong&gt;: Query the blockchain manually.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I tried the self-host route first. Here is what I learned, and the solution I eventually built to fix it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "Hard" Way: Polling with TronWeb
&lt;/h2&gt;

&lt;p&gt;To listen for payments, you typically use the tronweb library to interact with the Tron network.&lt;/p&gt;

&lt;p&gt;You need to poll the most recent transactions or set up an event listener. Here is a simplified version of the script I initially wrote:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const TronWeb = require('tronweb');
// You need an API Key from TronGrid, or you'll get rate-limited instantly.
const tronWeb = new TronWeb({
    fullHost: '[https://api.trongrid.io](https://api.trongrid.io)',
    headers: { "TRON-PRO-API-KEY": 'your-api-key' }
});

const USDT_CONTRACT = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"; // USDT Address

async function checkRecentTransactions(myWalletAddress) {
    // This is where it gets messy.
    // Public nodes often time out or return incomplete data.
    try {
        const contract = await tronWeb.contract().at(USDT_CONTRACT);
        // You have to parse the Transfer event logs
        // And handle hex conversion for amounts
        console.log("Listening for events...");

        // ... complex logic to filter events by 'to' address ...

    } catch (error) {
        console.error("RPC Error:", error);
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Problem with this approach
&lt;/h3&gt;

&lt;p&gt;It works locally, but in production:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rate Limits&lt;/strong&gt;: The free TronGrid API limits you heavily. If you have 10 users checking payments, you get banned.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Missed Events&lt;/strong&gt;: Public WebSocket connections drop all the time. You need robust retry logic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Maintenance&lt;/strong&gt;: You are basically maintaining a mini-indexer just to receive $20.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The "Easy" Way: Webhooks (ChainHook)
&lt;/h2&gt;

&lt;p&gt;I got tired of debugging RPC errors at 3 AM, so I decided to wrap this entire logic into a dedicated microservice.&lt;/p&gt;

&lt;p&gt;I built &lt;strong&gt;ChainHook&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It's a "Set and Forget" middleware.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;You give it a wallet address (Public key only).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It monitors the chain using premium nodes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It sends &lt;strong&gt;a Webhook (POST)&lt;/strong&gt; to your bot when a payment confirms.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Integration Example
&lt;/h3&gt;

&lt;p&gt;Instead of maintaining the script above, your bot just needs one endpoint to receive the data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Your Bot's Backend (Express/Fastify/Python)
app.post('/webhook/payment', (req, res) =&amp;gt; {
  const { event, data } = req.body;

  if (event === 'payment.confirmed') {
    const { amount, from, tx_hash } = data;

    console.log(`Received ${amount} USDT from ${from}`);
    console.log(`Hash: ${tx_hash}`);

    // TODO: Auto-deliver product to user
  }

  res.status(200).send('OK');
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The payload looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "event": "payment.confirmed",
  "data": {
    "network": "TRC20",
    "currency": "USDT",
    "amount": "29.99",
    "tx_hash": "89d3a...",
    "from": "TMwF...",
    "to": "YOUR_WALLET_ADDRESS",
    "timestamp": 171092834
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Looking for Beta Testers
&lt;/h2&gt;

&lt;p&gt;I built this primarily for myself and a few friends running Telegram bots, but I'm opening it up for a public beta.&lt;/p&gt;

&lt;p&gt;If you are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Building a Telegram Bot / Discord Bot&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Selling digital goods&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hate manual verification (or fake screenshot scams)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'd love for you to try it out. It's non-custodial, meaning the money goes straight to your wallet—I just act as the notifier.&lt;/p&gt;

&lt;p&gt;👉 Join the waiting list here: [&lt;a href="https://chainhook-landing.vercel.app/" rel="noopener noreferrer"&gt;https://chainhook-landing.vercel.app/&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;(Early testers will get lifetime access to the starter plan for free).&lt;/p&gt;

&lt;p&gt;Let me know in the comments if you have questions about handling Tron transactions manually, happy to help with the code too!&lt;/p&gt;

</description>
      <category>web3</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
