<?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>Forem: Dima Zaichenko</title>
    <description>The latest articles on Forem by Dima Zaichenko (@dima-zaichenko).</description>
    <link>https://forem.com/dima-zaichenko</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3283984%2F883e8406-f345-4159-b656-d77acf75e129.png</url>
      <title>Forem: Dima Zaichenko</title>
      <link>https://forem.com/dima-zaichenko</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/dima-zaichenko"/>
    <language>en</language>
    <item>
      <title>How I Added On-Chain Rewards and NFTs to Solana Quiz: Practical Insights, Pitfalls, and Tips</title>
      <dc:creator>Dima Zaichenko</dc:creator>
      <pubDate>Tue, 18 Nov 2025 08:32:05 +0000</pubDate>
      <link>https://forem.com/dima-zaichenko/how-i-added-on-chain-rewards-and-nfts-to-solana-quiz-practical-insights-pitfalls-and-tips-2l51</link>
      <guid>https://forem.com/dima-zaichenko/how-i-added-on-chain-rewards-and-nfts-to-solana-quiz-practical-insights-pitfalls-and-tips-2l51</guid>
      <description>&lt;p&gt;In my previous article on Dev.to, I shared how I built a small app - &lt;strong&gt;Solana Quiz&lt;/strong&gt;, where users answer daily questions and earn rewards. &lt;/p&gt;

&lt;p&gt;Here’s the link to that article (to skip architecture and basic setup):&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://dev.to/dima-zaichenko/how-i-combined-openai-rust-and-solana-a-quiz-that-pays-you-in-tokens-4ika"&gt;Previous Article&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since then, I’ve significantly expanded the functionality: I added &lt;strong&gt;on-chain rewards&lt;/strong&gt;, &lt;strong&gt;7-day streaks&lt;/strong&gt;, and the most exciting part - &lt;strong&gt;NFTs for a series of perfect answers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At first glance, it seems simple: trigger an event → reward tokens → sometimes issue an NFT.&lt;br&gt;&lt;br&gt;
But that "sometimes" hides a mountain of nuances: from &lt;strong&gt;Solana PDAs&lt;/strong&gt; to the order of metadata calls and weird errors not explained anywhere officially.&lt;/p&gt;

&lt;p&gt;In this article, I’ll share:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How I implemented on-chain/off-chain rewards
&lt;/li&gt;
&lt;li&gt;Why streaks behave differently in these modes
&lt;/li&gt;
&lt;li&gt;How NFT rewards work
&lt;/li&gt;
&lt;li&gt;Common pitfalls I encountered
&lt;/li&gt;
&lt;li&gt;What you need to know about the correct command sequence when creating NFTs (if you change the order, everything breaks)&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;🟡 &lt;strong&gt;How Solana Quiz Works Now&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a user completes a quiz (currently always 5 questions, hardcoded; soon configurable in &lt;code&gt;.env&lt;/code&gt;), the app:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sends the result to &lt;strong&gt;Kafka&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Rust service receives the event and grants the reward
&lt;/li&gt;
&lt;li&gt;Depending on the mode (&lt;code&gt;SOLANA_ON_CHAIN=true/false&lt;/code&gt;), the reward is applied:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;On-chain&lt;/strong&gt; → Solana transaction&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Off-chain&lt;/strong&gt; → Local API
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Rust confirms the reward via Kafka
&lt;/li&gt;
&lt;li&gt;Node.js marks the reward as issued
&lt;/li&gt;
&lt;li&gt;If a &lt;strong&gt;7-day streak&lt;/strong&gt; of perfect answers is achieved → &lt;strong&gt;NFT is minted&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fecabhmregp996gdaxk9v.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%2Fecabhmregp996gdaxk9v.png" alt="Solana Quiz Streaker: 7 Days" width="736" height="1216"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;🧰 &lt;strong&gt;Working with Solana &lt;code&gt;test-validator&lt;/code&gt; + Transaction Simulation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;🚀 &lt;strong&gt;Why use &lt;code&gt;solana-test-validator&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When I started writing the on-chain part of the quiz, I quickly realized: testing everything directly on devnet is painful.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transactions confirm slower
&lt;/li&gt;
&lt;li&gt;It’s hard to tell if an error is mine or the network’s
&lt;/li&gt;
&lt;li&gt;Some errors (especially PDA / metadata) are hard to catch in production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Solana has an amazing tool - the &lt;strong&gt;local validator&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;solana-test-validator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a full local Solana chain, launched with a single command.  &lt;/p&gt;

&lt;p&gt;Benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fast, infinite tokens
&lt;/li&gt;
&lt;li&gt;Deploy programs locally without restrictions
&lt;/li&gt;
&lt;li&gt;RPC available by default: &lt;code&gt;http://127.0.0.1:8899&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now you can point your app to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SOLANA_NETWORK=local
SOLANA_RPC_ENDPOINT=http://127.0.0.1:8899 # http://host.docker.internal:8899
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Writing your Solana programs (Anchor or pure Rust)
&lt;/li&gt;
&lt;li&gt;Testing mint NFT / PDA / metadata
&lt;/li&gt;
&lt;li&gt;Simulating transactions in bulk
&lt;/li&gt;
&lt;li&gt;Reproducing errors quickly and consistently&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;🧪 &lt;strong&gt;Devnet, Localnet, Mainnet - What’s the Difference?&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Environment&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Localnet (&lt;code&gt;solana-test-validator&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Instant dev&lt;/td&gt;
&lt;td&gt;Instant blocks, full control&lt;/td&gt;
&lt;td&gt;Not reflective of real network&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Devnet&lt;/td&gt;
&lt;td&gt;Pre-prod / public tests&lt;/td&gt;
&lt;td&gt;Real load, public RPC&lt;/td&gt;
&lt;td&gt;Sometimes slow, has limits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mainnet&lt;/td&gt;
&lt;td&gt;Production&lt;/td&gt;
&lt;td&gt;Stable, real tokenomics&lt;/td&gt;
&lt;td&gt;Expensive transactions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;💡 Recommended workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;80% of the time → work on &lt;strong&gt;test-validator&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;20% → final checks on &lt;strong&gt;devnet&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;🔍 &lt;strong&gt;Simulating Transactions &amp;amp; Getting Readable Errors&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Without simulations, you lose half the debugging info. Simulation allows you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Catch errors before sending
&lt;/li&gt;
&lt;li&gt;See program logs
&lt;/li&gt;
&lt;li&gt;Read SPL token, Metaplex errors, etc.
&lt;/li&gt;
&lt;li&gt;Check which PDAs don’t match
&lt;/li&gt;
&lt;li&gt;Verify if you have enough signers
&lt;/li&gt;
&lt;li&gt;See on-chain stack traces&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;simulation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.rpc_client&lt;/span&gt;&lt;span class="nf"&gt;.simulate_transaction&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;transaction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Simulation logs: {:#?}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;simulation&lt;/span&gt;&lt;span class="py"&gt;.value.logs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These lines saved me a lot of time.&lt;/p&gt;




&lt;p&gt;🔄 &lt;strong&gt;On-chain vs Off-chain: What &lt;code&gt;SOLANA_ON_CHAIN&lt;/code&gt; Really Controls&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A common mistake is thinking this variable enables or disables rewards.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No.&lt;/strong&gt; Rewards are always calculated. It only affects &lt;strong&gt;how the reward is applied&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;SOLANA_ON_CHAIN&lt;/th&gt;
&lt;th&gt;Token Calculation&lt;/th&gt;
&lt;th&gt;Streak Calculation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;On-chain (Solana transaction)&lt;/td&gt;
&lt;td&gt;Based on successful on-chain tx&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;Off-chain (via Rust API)&lt;/td&gt;
&lt;td&gt;Locally in Rust, without interacting with Solana&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Why? Streak logic must be consistent. On-chain → real transactions. Off-chain → faster and cheaper to calculate locally.&lt;/p&gt;




&lt;p&gt;🧩 &lt;strong&gt;NFT Rewards for Streaks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;NFTs are issued for &lt;strong&gt;7-day streaks of perfect answers&lt;/strong&gt; (&lt;code&gt;SOLANA_STREAK_DAYS=7&lt;/code&gt;).  &lt;/p&gt;

&lt;p&gt;Future plans: rarities, levels, different types of NFTs. For now, only one type is implemented.&lt;/p&gt;

&lt;p&gt;Technically, NFT creation process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new &lt;strong&gt;mint&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Create &lt;strong&gt;ATA&lt;/strong&gt; for the user
&lt;/li&gt;
&lt;li&gt;Mint 1 token to the ATA
&lt;/li&gt;
&lt;li&gt;Create &lt;strong&gt;metadata&lt;/strong&gt; (with master edition)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here begins the magic - or rather, the pain.&lt;/p&gt;

&lt;p&gt;🔥 &lt;strong&gt;Main Pain: Command Order Matters&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you look at Metaplex or Solana SDK examples, you won’t see warnings about call order. I didn’t either.  &lt;/p&gt;

&lt;p&gt;In my real project, here’s what happened:&lt;/p&gt;

&lt;p&gt;❌ Doing it incorrectly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nf"&gt;create_metadata&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nf"&gt;mint_token&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result: NFT often didn’t create; errors like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;"mint must have exactly 1 token minted before metadata creation"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;"MasterEdition account does not match mint supply"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Or transaction failed silently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;✔ Working order (experimentally verified):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nf"&gt;mint_token&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nf"&gt;create_metadata&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// metadata is created (along with the master edition)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why this order?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mint must have &lt;strong&gt;supply = 1&lt;/strong&gt; before metadata creation
&lt;/li&gt;
&lt;li&gt;ATA must exist beforehand; otherwise &lt;code&gt;mint_to_checked&lt;/code&gt; has no destination
&lt;/li&gt;
&lt;li&gt;MasterEdition can only be created after successful &lt;code&gt;mint_to_checked&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;All encapsulated in &lt;code&gt;create_metadata()&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I tried many variations; only this order works reliably, regardless of RPC or environment.&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Signers Matter&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Errors often happen if authority is incorrect or keypair not signed. Any transaction without the correct signer will fail or return weird errors.&lt;/p&gt;

&lt;p&gt;💡 Tip: Use transaction simulation to see all errors and warnings in readable form, without wasting SOL on devnet or mainnet.&lt;/p&gt;




&lt;p&gt;🧠 &lt;strong&gt;Why NFTs at All?&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Motivates users - 7-day streak is a small achievement, but tangible rewards feel nice
&lt;/li&gt;
&lt;li&gt;Great way to learn Metaplex &amp;amp; Solana SDK deeper
&lt;/li&gt;
&lt;li&gt;GitHub showcase - Rust + Solana + Node.js + Kafka interaction&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Repository: &lt;a href="https://github.com/di-zed/solana-quiz" rel="noopener noreferrer"&gt;Solana Quiz GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Solana program: &lt;a href="https://github.com/di-zed/solana-quiz/blob/main/rust/programs/solana_quiz_rewards/programs/solana_quiz_rewards/src/lib.rs" rel="noopener noreferrer"&gt;lib.rs&lt;/a&gt; &lt;br&gt;
Example logs: &lt;a href="https://solscan.io/tx/2nr9QGfE2WVj2udFMdjmUWYvvatMT1BoHuouwEqnGg7VKwLtoWkZcsGvK5bMH6SbDgN6T7n5hNi1WuQcFdutBXoY?cluster=devnet" rel="noopener noreferrer"&gt;Solscan tx&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;NFT API: &lt;a href="https://github.com/di-zed/solana-quiz/blob/main/rust/src/services/nft_api.rs" rel="noopener noreferrer"&gt;nft_api.rs&lt;/a&gt; &lt;br&gt;
Example logs: &lt;a href="https://solscan.io/tx/uomvJ7LJBDrQp74RUAgRve1MbweyPjJSDvnivkAk93AggygDnbrKKBMLu3TYEgQ4RkihKzfbdjURBz8wojkPNhb?cluster=devnet" rel="noopener noreferrer"&gt;Solscan tx&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;🎯 &lt;strong&gt;Other Technical Notes&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PDA for Metadata &amp;amp; MasterEdition must be calculated manually. No convenient module exists (&lt;code&gt;mpl_token_metadata::pda&lt;/code&gt;)
&lt;/li&gt;
&lt;li&gt;Mint authority should exist only until token issuance
&lt;/li&gt;
&lt;li&gt;ATA for NFT is always unique (NFT = 1 token, 0 decimals). Errors occur if decimals != 0&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;🎯 &lt;strong&gt;Future Expansion&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Different NFT types for different streak lengths
&lt;/li&gt;
&lt;li&gt;On-chain streak counting fully in Anchor program
&lt;/li&gt;
&lt;li&gt;Reward levels
&lt;/li&gt;
&lt;li&gt;NFT collections
&lt;/li&gt;
&lt;li&gt;mainnet-beta support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Currently, one NFT type &amp;amp; 7-day streak, but architecture allows painless future expansion.&lt;/p&gt;




&lt;p&gt;⚡ &lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I went through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On-chain/off-chain reward logic
&lt;/li&gt;
&lt;li&gt;Full NFT creation on Solana
&lt;/li&gt;
&lt;li&gt;Manual PDA calculation
&lt;/li&gt;
&lt;li&gt;Metaplex errors not documented anywhere
&lt;/li&gt;
&lt;li&gt;Experimentally determined correct NFT creation sequence
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now Solana Quiz is not just a game - it’s a service where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tokens are awarded honestly and transparently
&lt;/li&gt;
&lt;li&gt;Streaks are tracked correctly
&lt;/li&gt;
&lt;li&gt;Users can earn a small but real NFT for persistence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check the working code or fork the project: 👉 &lt;a href="https://github.com/di-zed/solana-quiz" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>solana</category>
      <category>web3</category>
      <category>openai</category>
      <category>rust</category>
    </item>
    <item>
      <title>🧠 How I Combined OpenAI, Rust, and Solana: a Quiz That Pays You in Tokens</title>
      <dc:creator>Dima Zaichenko</dc:creator>
      <pubDate>Mon, 13 Oct 2025 20:06:14 +0000</pubDate>
      <link>https://forem.com/dima-zaichenko/how-i-combined-openai-rust-and-solana-a-quiz-that-pays-you-in-tokens-4ika</link>
      <guid>https://forem.com/dima-zaichenko/how-i-combined-openai-rust-and-solana-a-quiz-that-pays-you-in-tokens-4ika</guid>
      <description>&lt;p&gt;What if every correct answer in a quiz rewarded you with crypto?  &lt;/p&gt;

&lt;p&gt;I decided to test this idea and built &lt;strong&gt;Solana Quiz&lt;/strong&gt; - a decentralized app where users answer daily quiz questions generated by OpenAI and earn &lt;strong&gt;Solana tokens&lt;/strong&gt; for correct answers.  &lt;/p&gt;

&lt;p&gt;Yes, it really works. And yes - we even &lt;strong&gt;mint our own token 😎&lt;/strong&gt;.  &lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 The Idea
&lt;/h2&gt;

&lt;p&gt;I’ve long wanted to combine &lt;strong&gt;AI, Rust, and blockchain&lt;/strong&gt; into a single project where each piece actually works, not just “for show.”  &lt;/p&gt;

&lt;p&gt;The result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI&lt;/strong&gt; generates fresh questions daily.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js&lt;/strong&gt; checks answers and handles the business logic.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rust&lt;/strong&gt; mints tokens on Solana.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kafka&lt;/strong&gt; ties all the services together in a clean event-driven system.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The outcome? A smart, fair, and transparent quiz where rewards don’t land in your email - they land in your &lt;strong&gt;Phantom Wallet&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 How It Works
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1️⃣ Solana Wallet Authentication
&lt;/h3&gt;

&lt;p&gt;Users connect their &lt;strong&gt;Phantom Wallet&lt;/strong&gt; and sign a message. Node.js verifies the signature and confirms wallet ownership.  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;No passwords, tokens, or logins - just pure blockchain authentication.  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Currently, only Phantom Wallet is supported, but adding more wallets is possible.&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%2Fa1e50f0w1xftmveqpfix.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%2Fa1e50f0w1xftmveqpfix.png" alt=" " width="540" height="515"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  2️⃣ Questions Powered by OpenAI
&lt;/h3&gt;

&lt;p&gt;Every morning, Node.js queries the &lt;strong&gt;OpenAI API&lt;/strong&gt; (&lt;code&gt;gpt-4.1-nano&lt;/code&gt;) to get a new set of questions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OPEN_AI_API_KEY=your_openai_api_key_here
OPEN_AI_MODEL=gpt-4.1-nano
OPEN_AI_LANGUAGE=English
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💡 You need an &lt;strong&gt;OpenAI account and API key&lt;/strong&gt;. Without it, the quiz stays silent 🤖  &lt;/p&gt;

&lt;p&gt;The questions are varied: from blockchain history to logic puzzles.&lt;/p&gt;




&lt;h3&gt;
  
  
  3️⃣ Checking Answers &amp;amp; Rewards
&lt;/h3&gt;

&lt;p&gt;Once the quiz is submitted, Node.js publishes an event to &lt;strong&gt;Kafka&lt;/strong&gt;, and the Rust worker listens for it. It then connects to &lt;strong&gt;Solana RPC&lt;/strong&gt; and… 💸 sends tokens straight to your wallet.  &lt;/p&gt;

&lt;p&gt;Every correct answer instantly becomes real crypto on the blockchain.&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%2Fgc0y68634qtb8pc9if31.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%2Fgc0y68634qtb8pc9if31.png" alt=" " width="374" height="614"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🏗 Architecture
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Technologies&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Frontend&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Next.js, React, TailwindCSS&lt;/td&gt;
&lt;td&gt;User interface and wallet integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Backend&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Node.js, Express, Prisma, KafkaJS, OpenAI API&lt;/td&gt;
&lt;td&gt;Quiz logic, question generation, API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rust Worker&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Rust, Solana SDK, rdkafka&lt;/td&gt;
&lt;td&gt;Token minting and Solana transactions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Database&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;td&gt;User data and quiz results storage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Infra&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Docker Compose&lt;/td&gt;
&lt;td&gt;Service orchestration&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🪙 Creating Your Own Token
&lt;/h2&gt;

&lt;p&gt;The heart of the quiz is a &lt;strong&gt;custom SPL token on Solana&lt;/strong&gt;. This isn’t just a demo coin - it powers the whole quiz economy and makes the reward system &lt;strong&gt;transparent and decentralized&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;⚠️ Before running the Rust commands, make sure your &lt;strong&gt;.env&lt;/strong&gt; file is ready:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SOLANA_NETWORK=devnet
SOLANA_RPC_ENDPOINT=https://api.devnet.solana.com
SOLANA_AUTHORITY_KEYPAIR_PATH=./secret/authority.json
SOLANA_MINT_KEYPAIR_PATH=./secret/mint.json
SOLANA_TOKEN_NAME="Solana Quiz Token"
SOLANA_TOKEN_SYMBOL="SQT"
SOLANA_TOKEN_METADATA_URI="https://raw.githubusercontent.com/di-zed/internal-storage/refs/heads/main/solana-quiz-token/metadata.json"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💡 Important: You can change the &lt;strong&gt;token name, symbol, description, and metadata&lt;/strong&gt; - it’s your token, your universe.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 Step-by-step: Minting your own Solana token
&lt;/h2&gt;

&lt;p&gt;Let's go through the full process — from building the binary to minting your first million tokens 🪙&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;# 🦀 Enter the Rust container&lt;/span&gt;
docker-compose &lt;span class="nb"&gt;exec &lt;/span&gt;rust /bin/bash

&lt;span class="c"&gt;# ⚙️ Build the release binary&lt;/span&gt;
cargo build &lt;span class="nt"&gt;--release&lt;/span&gt;

&lt;span class="c"&gt;# 💧 Request some SOL for your authority account&lt;/span&gt;
./target/release/solana request-airdrop &lt;span class="nt"&gt;--sol-amount&lt;/span&gt; 5

&lt;span class="c"&gt;# 🪙 Create a new token mint&lt;/span&gt;
./target/release/solana create-mint

&lt;span class="c"&gt;# 💼 Create a token account to store your tokens&lt;/span&gt;
./target/release/solana create-token-account

&lt;span class="c"&gt;# 💰 Mint 1,000,000 tokens&lt;/span&gt;
./target/release/solana mint-tokens &lt;span class="nt"&gt;--amount&lt;/span&gt; 1000000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💡 These tokens are your reward bank —&lt;br&gt;
every time someone answers correctly in the quiz, the Rust worker distributes tokens from this pool.&lt;/p&gt;

&lt;p&gt;To make your token display nicely in Phantom Wallet and Solana Explorer, add metadata such as:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://raw.githubusercontent.com/di-zed/internal-storage/refs/heads/main/solana-quiz-token/metadata.json" rel="noopener noreferrer"&gt;https://raw.githubusercontent.com/di-zed/internal-storage/refs/heads/main/solana-quiz-token/metadata.json&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 Why Rust
&lt;/h2&gt;

&lt;p&gt;Rust is perfect for working with the Solana SDK - safe, async-friendly, and memory-safe.  &lt;/p&gt;

&lt;p&gt;Even if you were a bit intimidated by Rust before, this project will make it your new &lt;strong&gt;crypto buddy 🦀&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  💎 Use Cases
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🎓 &lt;strong&gt;EdTech&lt;/strong&gt;: tokenize learning - correct answers = real rewards.
&lt;/li&gt;
&lt;li&gt;🏢 &lt;strong&gt;Corporate quizzes&lt;/strong&gt;: motivate employees with tokens.
&lt;/li&gt;
&lt;li&gt;🌐 &lt;strong&gt;Crypto communities&lt;/strong&gt;: daily activities and rewards on Solana.
&lt;/li&gt;
&lt;li&gt;💰 &lt;strong&gt;Loyalty &amp;amp; gamification&lt;/strong&gt;: on-chain incentives for engagement.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧭 Roadmap
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🏆 &lt;strong&gt;NFT rewards&lt;/strong&gt; - issue NFTs for streaks of correct answers.
&lt;/li&gt;
&lt;li&gt;🔗 &lt;strong&gt;Solana Program&lt;/strong&gt; - store quiz history and auto-distribute NFTs.
&lt;/li&gt;
&lt;li&gt;🚀 &lt;strong&gt;Mainnet-beta migration&lt;/strong&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Solana program will eventually track quiz history on-chain and automatically reward users who maintain a streak (e.g., 7 days in a row).  &lt;/p&gt;

&lt;p&gt;NFTs can be created manually via &lt;strong&gt;Metaplex&lt;/strong&gt; - your collection, your designs, your rewards 🎨&lt;/p&gt;




&lt;h2&gt;
  
  
  🧩 A Bit of Technical Info
&lt;/h2&gt;

&lt;p&gt;I didn’t dive into setup commands in this article - we want it to be easy to read 😄  &lt;/p&gt;

&lt;p&gt;If you want to &lt;strong&gt;actually run the project&lt;/strong&gt;, mint your own token, connect OpenAI, and see Solana deliver real rewards - check the &lt;strong&gt;README on GitHub&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;It has step-by-step instructions, &lt;code&gt;.env&lt;/code&gt; examples, and Docker, Rust, and Node.js commands - everything you need to dig deeper.&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;Solana Quiz&lt;/strong&gt; isn’t a demo or experiment. It’s a &lt;strong&gt;live app&lt;/strong&gt; where AI, Rust, and blockchain work together:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI&lt;/strong&gt; generates content
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js&lt;/strong&gt; manages logic
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kafka&lt;/strong&gt; connects everything
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rust&lt;/strong&gt; handles real transactions
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result? A &lt;strong&gt;fair reward system&lt;/strong&gt; where knowledge brings you &lt;strong&gt;crypto&lt;/strong&gt;, not just points.&lt;/p&gt;




&lt;p&gt;⚙️ The project runs entirely via &lt;strong&gt;Docker&lt;/strong&gt; - clone the repo, follow the README, and you’re good to go. Everything is covered, including key generation and OpenAI/Solana setup.&lt;/p&gt;

&lt;p&gt;📦 GitHub: &lt;a href="https://github.com/di-zed/solana-quiz" rel="noopener noreferrer"&gt;github.com/di-zed/solana-quiz&lt;/a&gt;&lt;br&gt;&lt;br&gt;
📜 License: MIT&lt;br&gt;&lt;br&gt;
✍️ Author: @di-zed&lt;/p&gt;

</description>
      <category>solana</category>
      <category>web3</category>
      <category>openai</category>
      <category>rust</category>
    </item>
    <item>
      <title>Telegram AI Companion: A Fun Rust + Local AI + Telegram Project</title>
      <dc:creator>Dima Zaichenko</dc:creator>
      <pubDate>Tue, 24 Jun 2025 07:09:05 +0000</pubDate>
      <link>https://forem.com/dima-zaichenko/telegram-ai-companion-a-fun-rust-local-ai-telegram-project-1im2</link>
      <guid>https://forem.com/dima-zaichenko/telegram-ai-companion-a-fun-rust-local-ai-telegram-project-1im2</guid>
      <description>&lt;p&gt;Hey dev.to! 👋&lt;/p&gt;

&lt;p&gt;Recently I built a small but fun pet project — &lt;strong&gt;Telegram AI Companion&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
It's a Telegram bot that chats with you using a local LLM via LocalAI.&lt;br&gt;&lt;br&gt;
No OpenAI, no clouds — everything runs on your own machine! 🧠💻&lt;/p&gt;

&lt;p&gt;The goal? Not to reinvent AI, but to explore &lt;strong&gt;Rust&lt;/strong&gt;, &lt;strong&gt;async programming&lt;/strong&gt;, &lt;strong&gt;Telegram API&lt;/strong&gt;, and &lt;strong&gt;local LLMs&lt;/strong&gt;. Think of it as a “developer's companion bot”. 😄&lt;/p&gt;


&lt;h2&gt;
  
  
  🧩 What It Can Do
&lt;/h2&gt;

&lt;p&gt;✅ Replies to any message in Telegram&lt;br&gt;&lt;br&gt;
✅ Works with &lt;strong&gt;LocalAI&lt;/strong&gt; (or OpenAI if you want)&lt;br&gt;&lt;br&gt;
✅ Runs via &lt;strong&gt;Docker + Docker Compose&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
✅ Written in &lt;strong&gt;Rust&lt;/strong&gt; with &lt;strong&gt;Actix Web&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
✅ Has a REST API (&lt;code&gt;/chat&lt;/code&gt;) — hook up any UI&lt;br&gt;&lt;br&gt;
✅ Includes tests and has a clean project structure&lt;/p&gt;


&lt;h2&gt;
  
  
  ⚙️ How It Works
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Overview
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;User sends a message to the Telegram bot
&lt;/li&gt;
&lt;li&gt;Telegram calls our webhook (&lt;code&gt;/telegram/webhook&lt;/code&gt;)
&lt;/li&gt;
&lt;li&gt;Rust app sends the prompt to LocalAI
&lt;/li&gt;
&lt;li&gt;Gets a reply and sends it back to the user
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Tech Stack
&lt;/h3&gt;

&lt;p&gt;🦀 &lt;strong&gt;Rust&lt;/strong&gt; — strict but powerful&lt;br&gt;&lt;br&gt;
🌐 &lt;strong&gt;Actix Web&lt;/strong&gt; — high-perf async framework&lt;br&gt;&lt;br&gt;
📦 &lt;strong&gt;Docker &amp;amp; Compose&lt;/strong&gt; — clean and reproducible&lt;br&gt;&lt;br&gt;
🧠 &lt;strong&gt;LocalAI&lt;/strong&gt; — local alternative to OpenAI, supports GGUF/LLaMa&lt;br&gt;&lt;br&gt;
☁️ Optional: &lt;strong&gt;OpenAI&lt;/strong&gt; support via &lt;code&gt;.env&lt;/code&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  🚀 Quickstart
&lt;/h2&gt;

&lt;p&gt;Clone the repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/di-zed/tg-ai-companion
&lt;span class="nb"&gt;cd &lt;/span&gt;tg-ai-companion
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Download a model (e.g., Mistral 7B) and configure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;models/
wget https://huggingface.co/TheBloke/Mistral-7B-Instruct-v0.2-GGUF/resolve/main/mistral-7b-instruct-v0.2.Q4_K_M.gguf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;code&gt;mistral.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mistral&lt;/span&gt;
&lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;llama&lt;/span&gt;
&lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mistral-7b-instruct-v0.2.Q4_K_M.gguf&lt;/span&gt;
  &lt;span class="na"&gt;temperature&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.7&lt;/span&gt;
  &lt;span class="na"&gt;top_p&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.9&lt;/span&gt;
  &lt;span class="na"&gt;top_k&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;40&lt;/span&gt;
  &lt;span class="na"&gt;n_ctx&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4096&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or configure OpenAI in &lt;code&gt;.env&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OPEN_AI_URL=http://localai:8080
OPEN_AI_MODEL=mistral
OPEN_AI_API_KEY=your_openai_key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the app (don't forget to edit &lt;code&gt;.env&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; .env.sample .env
&lt;span class="nb"&gt;cp &lt;/span&gt;volumes/root/.bash_history.sample volumes/root/.bash_history

docker-compose up &lt;span class="nt"&gt;--build&lt;/span&gt;
docker-compose &lt;span class="nb"&gt;exec &lt;/span&gt;rust bash
cargo run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your bot runs locally, and LocalAI listens on &lt;code&gt;localhost:8080&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🤖 Create Your Telegram Bot
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open Telegram and talk to &lt;a href="https://t.me/BotFather" rel="noopener noreferrer"&gt;@BotFather&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;/newbot&lt;/code&gt;, set a name and a unique username (&lt;code&gt;something_bot&lt;/code&gt;)
&lt;/li&gt;
&lt;li&gt;You'll get a token like:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;123456789:AAH6kDkKvkkkT-PWTwMg6cYtHEb3vY_tS1k
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste it into &lt;code&gt;.env&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TELEGRAM_BOT_TOKEN=your_token_here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🌍 Expose Webhook via ngrok
&lt;/h2&gt;

&lt;p&gt;Make your local server reachable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ngrok http 80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then set webhook:&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 &lt;span class="s2"&gt;"https://api.telegram.org/bot&amp;lt;YOUR_TOKEN&amp;gt;/setWebhook"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&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;'{"url": "https://your-subdomain.ngrok-free.app/telegram/webhook"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🔐 API Mode (No Telegram)
&lt;/h2&gt;

&lt;p&gt;You can also call it like a standard LLM API:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;POST /chat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Header: &lt;code&gt;Authorization: Bearer YOUR_TOKEN&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"prompt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hi, who are you?"&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;LocalAI (or OpenAI) responds.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 Why I Built This
&lt;/h2&gt;

&lt;p&gt;Main goals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Learn &lt;strong&gt;Rust&lt;/strong&gt; hands-on
&lt;/li&gt;
&lt;li&gt;Explore &lt;strong&gt;local LLMs&lt;/strong&gt; without API keys
&lt;/li&gt;
&lt;li&gt;Build something fun and useful
&lt;/li&gt;
&lt;li&gt;Play with &lt;strong&gt;Telegram bots&lt;/strong&gt; 😎&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This can be a base for future AI bots with memory, content generation, assistants, and more.&lt;/p&gt;




&lt;h2&gt;
  
  
  📅 What’s Next?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Memory + conversation context
&lt;/li&gt;
&lt;li&gt;Web interface
&lt;/li&gt;
&lt;li&gt;Multi-model support
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💬 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;If you're just starting with Rust or want to try local LLMs — this might be a perfect playground.&lt;br&gt;&lt;br&gt;
The code is clean, the stack is modern, and setup is smooth.&lt;/p&gt;

&lt;p&gt;I kept this post light — for deep dives, check the full README:&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/di-zed/tg-ai-companion" rel="noopener noreferrer"&gt;tg-ai-companion&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🔗 Useful Links
&lt;/h2&gt;

&lt;p&gt;🧠 &lt;a href="https://localai.io/" rel="noopener noreferrer"&gt;LocalAI&lt;/a&gt; — LLM backend&lt;br&gt;&lt;br&gt;
🦀 &lt;a href="https://doc.rust-lang.org/book/" rel="noopener noreferrer"&gt;Rust Book&lt;/a&gt; — start here&lt;br&gt;&lt;br&gt;
☁️ &lt;a href="https://ngrok.com/" rel="noopener noreferrer"&gt;ngrok&lt;/a&gt; — webhook tunneling&lt;/p&gt;

&lt;p&gt;Thanks for reading! 🙌&lt;br&gt;&lt;br&gt;
If the bot responds cheerfully — that’s on me.&lt;br&gt;&lt;br&gt;
If it’s silent — blame Telegram or ngrok 😄&lt;/p&gt;

</description>
      <category>rust</category>
      <category>telegram</category>
      <category>ai</category>
      <category>docker</category>
    </item>
    <item>
      <title>How to Automatically Renew Let’s Encrypt SSL Certs with Certbot on Ubuntu 🔐</title>
      <dc:creator>Dima Zaichenko</dc:creator>
      <pubDate>Mon, 23 Jun 2025 05:36:30 +0000</pubDate>
      <link>https://forem.com/dima-zaichenko/how-to-automatically-renew-lets-encrypt-ssl-certs-with-certbot-on-ubuntu-37c7</link>
      <guid>https://forem.com/dima-zaichenko/how-to-automatically-renew-lets-encrypt-ssl-certs-with-certbot-on-ubuntu-37c7</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“I’m not a DevOps engineer, but I play one in production.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Tired of manually renewing SSL certs every 90 days like it’s a Tamagotchi from 2005?&lt;br&gt;&lt;br&gt;
This guide is for you: developers who want HTTPS &lt;strong&gt;without&lt;/strong&gt; DevOps nightmares.&lt;/p&gt;


&lt;h2&gt;
  
  
  🤔 Why Certbot?
&lt;/h2&gt;

&lt;p&gt;Because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's &lt;strong&gt;free&lt;/strong&gt; (thanks, Let's Encrypt).&lt;/li&gt;
&lt;li&gt;It &lt;strong&gt;works&lt;/strong&gt; (thanks, Certbot).&lt;/li&gt;
&lt;li&gt;It &lt;strong&gt;renews itself&lt;/strong&gt; (mostly).&lt;/li&gt;
&lt;li&gt;And most importantly: you can get back to writing code instead of deciphering NGINX logs at 3AM.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  🧪 Step 1 — Install Certbot
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;certbot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Boom. That’s it. The easy part.&lt;/p&gt;


&lt;h2&gt;
  
  
  📜 Step 2 — Get your first SSL cert
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;certbot certonly &lt;span class="nt"&gt;--standalone&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; yourdomain.com &lt;span class="nt"&gt;-d&lt;/span&gt; www.yourdomain.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Check what you got:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;certbot certificates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;🧠 Heads-up: &lt;code&gt;--standalone&lt;/code&gt; runs a temporary HTTP server on port 80.&lt;br&gt;&lt;br&gt;
If you're using Nginx or Docker, &lt;strong&gt;stop them first&lt;/strong&gt; or use &lt;code&gt;--webroot&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwdprrukzbuzbr1qh1o7v.webp" 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%2Fwdprrukzbuzbr1qh1o7v.webp" alt=" " width="780" height="780"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More information about plugins can be found in the &lt;a href="https://eff-certbot.readthedocs.io/en/stable/using.html#getting-certificates-and-choosing-plugins" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔁 Step 3 — Let it renew itself (like magic)
&lt;/h2&gt;

&lt;p&gt;Certbot installs a systemd timer by default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl list-timers | &lt;span class="nb"&gt;grep &lt;/span&gt;certbot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see output like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Mon 2025-03-10 03:12:00 UTC  10h left  Mon 2025-03-09 03:12:00 UTC  certbot.timer  certbot.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test if it works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;certbot renew &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Certbot dry-run was successful.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;📅 Certbot renews certificates &lt;strong&gt;when they’re 30 days from expiry&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Latest update logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; certbot.service &lt;span class="nt"&gt;--no-pager&lt;/span&gt; &lt;span class="nt"&gt;--since&lt;/span&gt; &lt;span class="s2"&gt;"2 days ago"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🧓 Prefer old-school cron? No problem
&lt;/h2&gt;

&lt;p&gt;Disable the timer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl stop certbot.timer
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl disable certbot.timer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add this to your crontab (&lt;code&gt;sudo crontab -e&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0 0 1 * * /usr/bin/certbot renew --standalone --quiet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To find out the exact path to &lt;code&gt;certbot&lt;/code&gt;, use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;which certbot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because nothing says “legacy and proud” like a 1AM cron job.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧙 Use Hooks Like a Wizard
&lt;/h2&gt;

&lt;p&gt;Automatically stop/start your services when certs are renewed.&lt;/p&gt;

&lt;p&gt;Open your renewal config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/letsencrypt/renewal/yourdomain.com.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;pre_hook&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;systemctl stop your-app.service&lt;/span&gt;
&lt;span class="py"&gt;deploy_hook&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;systemctl reload your-app.service&lt;/span&gt;
&lt;span class="py"&gt;post_hook&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;systemctl start your-app.service&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Perfect when you want Certbot to renew certs &lt;strong&gt;and do the dishes.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🛠 Bonus: Bash Script of Champions
&lt;/h2&gt;

&lt;p&gt;This is what you actually wanted — a copy-pasteable script to handle everything:&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;#!/bin/bash&lt;/span&gt;
&lt;span class="nv"&gt;APP_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/home/you/yourapp"&lt;/span&gt;
&lt;span class="nv"&gt;DOMAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"yourdomain.com"&lt;/span&gt;

&lt;span class="nv"&gt;FULL_CHAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/etc/letsencrypt/live/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DOMAIN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/fullchain.pem"&lt;/span&gt;
&lt;span class="nv"&gt;PRIVATE_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/etc/letsencrypt/live/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DOMAIN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/privkey.pem"&lt;/span&gt;

&lt;span class="nv"&gt;CRT_DEST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;APP_PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/ssl/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DOMAIN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.crt"&lt;/span&gt;
&lt;span class="nv"&gt;KEY_DEST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;APP_PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/ssl/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DOMAIN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.key"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;] Stopping the service..."&lt;/span&gt;
systemctl stop your-app.service

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Renewing certs silently..."&lt;/span&gt;
/usr/bin/certbot renew &lt;span class="nt"&gt;--standalone&lt;/span&gt; &lt;span class="nt"&gt;--quiet&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Copying certs like a pro..."&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FULL_CHAIN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CRT_DEST&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PRIVATE_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KEY_DEST&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;644 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CRT_DEST&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KEY_DEST&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Starting the service again..."&lt;/span&gt;
systemctl start your-app.service

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"🎉 All done. Go grab a coffee."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Crontab it like a boss:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0 0 1 * * /bin/bash /home/you/scripts/renew-ssl.sh &amp;gt;&amp;gt; /var/log/certbot.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🧠 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;SSL shouldn't be harder than writing JavaScript regex.&lt;br&gt;&lt;br&gt;
Certbot + Let’s Encrypt is &lt;em&gt;one of those rare tools&lt;/em&gt; that actually “just works” — if you let it.&lt;/p&gt;

&lt;p&gt;✅ Works with cron&lt;br&gt;&lt;br&gt;
✅ Supports systemd&lt;br&gt;&lt;br&gt;
✅ Has deploy hooks&lt;br&gt;&lt;br&gt;
✅ Costs $0&lt;/p&gt;

&lt;p&gt;Just test it every now and then. Let automation do its thing.&lt;br&gt;&lt;br&gt;
And for the love of uptime — &lt;strong&gt;back up your certs&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  ✨ Bonus Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://letsencrypt.org/docs/" rel="noopener noreferrer"&gt;Let’s Encrypt Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://certbot.eff.org/" rel="noopener noreferrer"&gt;Certbot Official Site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ssllabs.com/ssltest/" rel="noopener noreferrer"&gt;SSL Labs Test Tool&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>certbot</category>
      <category>ssl</category>
      <category>ubuntu</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
