<?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: Daniel S</title>
    <description>The latest articles on Forem by Daniel S (@elkornacio).</description>
    <link>https://forem.com/elkornacio</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%2F2685967%2Ff6730c9a-1cef-4aa2-9641-102b98083160.jpeg</url>
      <title>Forem: Daniel S</title>
      <link>https://forem.com/elkornacio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/elkornacio"/>
    <language>en</language>
    <item>
      <title>Stop making bad meetups already</title>
      <dc:creator>Daniel S</dc:creator>
      <pubDate>Wed, 15 Jan 2025 17:27:56 +0000</pubDate>
      <link>https://forem.com/elkornacio/stop-making-bad-meetups-already-3j8b</link>
      <guid>https://forem.com/elkornacio/stop-making-bad-meetups-already-3j8b</guid>
      <description>&lt;p&gt;Offline meetups are getting more and more popular. Remote-first world forces people to look eagerly into offline socialization. Over the last few weeks, I’ve plunged into this world again: networking, startup pitch competitions, public speakers — a whirlwind of events has swept in.&lt;/p&gt;

&lt;p&gt;Unfortunately, around 70% of this whirlwind is crap.&lt;/p&gt;

&lt;p&gt;After attending yet another time-wasting event and being amazed once more at how organizers manage to so devotedly ignore all the principles of moderation and common sense, I realized I couldn’t stay silent.&lt;/p&gt;

&lt;p&gt;Back in the day, I organized and moderated more than a few events of all sorts of formats and sizes. I came up with a bunch of basic principles, and following them is what separates a good event from a bad one.&lt;/p&gt;

&lt;p&gt;Here they are.&lt;/p&gt;

&lt;h2&gt;
  
  
  0. The most important thing — moderation
&lt;/h2&gt;

&lt;p&gt;Moderation is incredibly important. It’s the &lt;strong&gt;crucial thing&lt;/strong&gt; for any good event. The moderator’s role is the cornerstone: it’s up to them whether people have a good, beneficial experience or just lose a couple of hours of their life.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens at meetups without moderation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;At networking sessions, introverts stare at their phones and leave without opening up or making new connections&lt;/li&gt;
&lt;li&gt;During pitch sessions, the loudest or boldest participants take all the floor time, leaving everyone else unheard&lt;/li&gt;
&lt;li&gt;People ask speakers irrelevant questions and waste everyone’s time&lt;/li&gt;
&lt;li&gt;Speakers get carried away, stray off-topic, and waste time or lose focus&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A good moderator, like a conductor, guides the event and maintains its quality.&lt;/p&gt;

&lt;p&gt;But how do you become a good moderator?&lt;/p&gt;

&lt;h2&gt;
  
  
  1. “Is what’s happening right now beneficial for the participants?”
&lt;/h2&gt;

&lt;p&gt;This is the universal question that a moderator should ask themselves every 10 seconds.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the speaker’s topic relevant to the audience? Maybe they need a subtle hint or nudge?&lt;/li&gt;
&lt;li&gt;Is the question someone asked truly important for everyone else? Maybe it’s too narrow, and you should politely say “no” and pass the microphone to the next person?&lt;/li&gt;
&lt;li&gt;During coffee breaks, are people actually getting to know one another or just drifting off into corners?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You need to understand who your audience is and why they came to your event. If, at any moment, they’re not getting value and you’re just sitting there too shy to guide the speaker or remind someone they’re over time, you’re effectively destroying your event and squandering dozens (or even hundreds) of human-hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Introduce people at networking sessions
&lt;/h2&gt;

&lt;p&gt;Networking isn’t just about bringing a lot of people together in one place. Networking is about creating a context where it’s &lt;strong&gt;easy&lt;/strong&gt; for them to connect. Ideally, these connections should form between people who can be useful to each other.&lt;/p&gt;

&lt;p&gt;A good networking event starts with the moderator introducing the participants. Give each person 20–30 seconds for a short intro. Begin with yourself — set the example.&lt;/p&gt;

&lt;p&gt;Are there too many people? Break them into groups of 8–10, then mix them up and form new circles.&lt;/p&gt;

&lt;p&gt;Thirty seconds for 10 people is only 5 minutes. But in those 5 minutes, everyone will mentally note who seems interesting and who they’d like to get to know better. Plus, it’s a great conversation starter: “Hey, you do online retail? That’s so familiar — as you heard, I build online stores. Can you tell me more?” is way better than the usual, “Hi, I’m John, what do you do?”&lt;/p&gt;

&lt;p&gt;Try experimenting: ask people to name the strangest thing about themselves or share their craziest fail. Little confessions like that often make great ground for future conversations.&lt;/p&gt;

&lt;p&gt;Test out different introduction games that create a relaxed context for chatting. That’s the essence of a networking meetup. Anyone can throw an event on Facebook, but real networking takes a bit more.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Keep track of speakers’ stories
&lt;/h2&gt;

&lt;p&gt;Not every talk is agreed upon beforehand. That can be great: you can do interviews, field questions from the audience, use lots of formats that let speakers explore topics freely without prepared slides or scripts.&lt;/p&gt;

&lt;p&gt;Seasoned speakers handle this just fine. But more often, the person is a professional in their field — design, development, management, whatever — and not necessarily a pro at public speaking. They might be nervous, lose their train of thought, go into irrelevant details, or stretch beyond the allotted time.&lt;/p&gt;

&lt;p&gt;Help them out. Don’t just sit there watching someone flounder while the audience quietly slips away. Yes, cutting in can feel uncomfortable, but it’s necessary: not only the speaker but also the audience (whose valuable time you’re saving) will thank you.&lt;/p&gt;

&lt;p&gt;There are plenty of ways to do it politely and tactfully:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Alex, this is incredibly interesting, but I’m afraid not many in the room are that deeply familiar with X. You mentioned Y, which I think is more common — could you talk a bit more about how you solved that?”&lt;/p&gt;

&lt;p&gt;“Marie, you know so much about X, and I’m sure you could talk about it for days. But unfortunately, we only have a couple of hours, so I have to narrow things down a bit: could you focus more on Y — it’s really popular right now?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Smile, be polite and kind. You’re not a disciplinarian telling someone off for going over time. You genuinely want to help them share their most valuable points with the audience.&lt;/p&gt;

&lt;p&gt;There’s nothing sadder than seeing speakers looking dejected at the end of an event, facing a half-empty room. It’s especially hard when people walk out during their talk. “I guess I got carried away with X… Maybe it wasn’t interesting to everyone…” But if there’d been a good moderator, things would have gone smoothly.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Moderate questions to the speakers
&lt;/h2&gt;

&lt;p&gt;Some event formats include open questions from the audience. Unfortunately, this is often where moderation fails: people ask super-niche questions relevant only to them, or toxic (passive-aggressive) questions, or weird off-topic questions, or they unload 10 questions at once.&lt;/p&gt;

&lt;p&gt;Firstly, for some events, that open format might not be the best fit. Got a pitch session? Put together a solid jury to ask questions and give feedback — not the audience. That ensures you get good, thoughtful, expert questions, and everyone can benefit from the answers.&lt;/p&gt;

&lt;p&gt;Okay, so your event format really does require audience interaction? Don’t be afraid to cut or reframe questions:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Sorry, but I’m afraid that’s too narrow an issue. It’s best if you catch the speaker afterwards and discuss it one-on-one.”&lt;/p&gt;

&lt;p&gt;“I’d like to expand your question: I think everyone would be curious not only about X, but about the whole segment in general.”&lt;/p&gt;

&lt;p&gt;“Sorry, but we already covered that a bit earlier.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And so on. An audience is chaos, full of lots of different people. Some struggle to phrase their question, others zone out and ask something that was already explained, etc.&lt;/p&gt;

&lt;p&gt;Don’t let your own shyness get the better of you — politely decline or redirect someone’s question and pass the mic. Everyone else in the room will thank you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Afterword
&lt;/h2&gt;

&lt;p&gt;Moderating an event isn’t difficult at all. Politeness, gentle guiding remarks, helping people open up — these are the main tools of a good moderator. Always keep in mind why people came to your event and continually help them achieve that.&lt;/p&gt;

&lt;p&gt;Remember that 15 minutes of wasted time in a meetup of 100 people adds up to a whole person-day lost for nothing.&lt;/p&gt;

</description>
      <category>meetup</category>
      <category>devdiscuss</category>
      <category>speaking</category>
    </item>
    <item>
      <title>TMA Wallet — a non-custodial MPC wallet for your Telegram Mini App</title>
      <dc:creator>Daniel S</dc:creator>
      <pubDate>Tue, 14 Jan 2025 15:09:46 +0000</pubDate>
      <link>https://forem.com/elkornacio/tma-wallet-a-non-custodial-mpc-wallet-for-your-telegram-mini-app-2f44</link>
      <guid>https://forem.com/elkornacio/tma-wallet-a-non-custodial-mpc-wallet-for-your-telegram-mini-app-2f44</guid>
      <description>&lt;p&gt;&lt;strong&gt;JavaScript · Cryptocurrencies · Cryptography&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;Hello everyone! I guess you already know that for about a year now there’s been a boom of mini apps in Telegram &lt;del&gt;everyone tapped on the hamster&lt;/del&gt;. Most of these mini apps are related to crypto. Many developers want to provide their users with a wallet inside the app (EVM, TON, Solana, etc.)—basically a virtual account that can be topped up, can withdraw funds, and most importantly, can call smart contracts.&lt;/p&gt;

&lt;p&gt;A simple but unsafe solution is to store all the keys on your server and make transactions on behalf of the user. If someone hacks your server, all client funds are lost. It’s hard to earn people’s trust in that scenario.&lt;/p&gt;

&lt;p&gt;A complex but inconvenient solution is a wallet that the user must write down on a piece of paper and manage by themselves. In that case, you might as well just use WalletConnect &lt;del&gt;or not build a mini app at all&lt;/del&gt;. The problem is that your mini app’s UI could become painful: the user would have to confirm every action in an external app.&lt;/p&gt;

&lt;p&gt;We looked for an option for our mini app that offers the security of a non-custodial wallet with the smoothest possible UX/UI. And we found it.&lt;/p&gt;

&lt;p&gt;In this article, I’ll review &lt;strong&gt;TMA Wallet&lt;/strong&gt; (&lt;a href="https://www.npmjs.com/package/@tmawallet/sdk" rel="noopener noreferrer"&gt;npm package&lt;/a&gt;, &lt;a href="https://tmawallet.com" rel="noopener noreferrer"&gt;website&lt;/a&gt;, &lt;a href="https://github.com/tmawallet/sdk" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)—an open-source, non-custodial, multi-party wallet suitable for any chain, which works using the recently introduced &lt;strong&gt;Telegram Cloud Storage&lt;/strong&gt; API.&lt;/p&gt;

&lt;p&gt;Let’s go!&lt;/p&gt;




&lt;h2&gt;
  
  
  Very Brief Explanation of Terms
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Wallet = Private Key&lt;/strong&gt;. This private key is used to sign transactions and grants its owner the right to control the funds at a specific blockchain address.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Custodial Wallet&lt;/strong&gt; = Some organization owns your private key and can act on your behalf. A classic example is a crypto exchange like Binance. It’s convenient but requires great trust in the organization.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Non-custodial Wallet&lt;/strong&gt; = You alone have your private key. It’s stored on your device, and all actions with your funds are done by you or with your confirmation. The main issue is that it’s easy to lose. If you lose your private key, you lose your funds.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;MPC (multi-party computation)&lt;/strong&gt; = An attempt to solve the “lost wallet” issue: the key is split into several parts, stored in different places, and all parts are needed to form a signature on a transaction. In this scenario, hacking one party doesn’t let you access the user’s funds. Meanwhile, the user doesn’t need to store the key entirely on their own.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, a &lt;strong&gt;non-custodial MPC wallet&lt;/strong&gt; is a wallet where the private key is split into parts stored in different locations and never fully assembled by any single party.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Exactly Is TMA Wallet?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;TMA Wallet&lt;/strong&gt; is a non-custodial, multi-party (MPC) wallet that uses Telegram Cloud Storage for secure key storage. Everything is linked to the user’s Telegram account, so they don’t have to remember any seed phrases or set up external wallets. The flow is so smooth that your user might not even realize there’s a crypto wallet under the hood—you can build a completely friendly UI and hide the blockchain magic from the user.&lt;/p&gt;

&lt;p&gt;Here are some of the main advantages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Easy Integration&lt;/strong&gt;: Just install the npm package, plug it into your code, and that’s it. Every user of your mini app now has a wallet.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No TON Connect or WalletConnect Workarounds&lt;/strong&gt;: The user stays entirely in Telegram; all transactions are signed “under the hood.”&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;MPC Technology&lt;/strong&gt;: The private key isn’t available to anyone—not Telegram, not your server, not TMA Wallet’s servers. It’s only put together on the user’s device for a few nanoseconds (while signing a transaction) and then disappears.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Easy Recovery&lt;/strong&gt;: Lost your phone? No problem—get a new one, log into Telegram, and the wallet is automatically restored.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Access from Multiple Devices&lt;/strong&gt;: If the user opens the mini app from a desktop client with the same Telegram account, they’ll get access to the same wallet as on their phone.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Open-Source&lt;/strong&gt;: Everything is on GitHub. You can review and verify security yourself or commission an audit.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Viem/Wagmi/Ethers.js Support&lt;/strong&gt;: If you’re working on any EVM-compatible chain (Ethereum, BSC, Polygon, etc.), you can use standard libraries.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Supports Any Chain&lt;/strong&gt;: EVM chains are supported out of the box, but TMA Wallet is basically a system for separate storage of any secret. So you could store a private key for TON, Solana, or any other chain.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  How Does It Work “Under the Hood”?
&lt;/h2&gt;

&lt;p&gt;As I’ve mentioned, TMA Wallet is based on MPC principles, where the private key is effectively shared between multiple parties and only reassembled briefly on the client side to sign transactions. Here’s a short summary:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;When the user first opens your mini app, the user’s device generates a &lt;strong&gt;ClientPublicKey&lt;/strong&gt; and &lt;strong&gt;ClientSecretKey&lt;/strong&gt;. The &lt;strong&gt;ClientSecretKey&lt;/strong&gt; is saved in &lt;strong&gt;Telegram Cloud Storage&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;ClientPublicKey&lt;/strong&gt; and &lt;code&gt;WebApp.initData&lt;/code&gt; (signed by Telegram) are sent to the server.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The server checks that Telegram’s signature is valid and (optionally) asks the user for extra authentication (2FA). It’s optional, and you don’t have to if you don’t want to.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The server then generates an &lt;strong&gt;IntermediaryKey&lt;/strong&gt; by signing (ClientPublicKey + telegramUserId) with its own &lt;strong&gt;ServerSecretKey&lt;/strong&gt;. Then it encrypts this &lt;strong&gt;IntermediaryKey&lt;/strong&gt; before sending it back to the client.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;IntermediaryKey&lt;/strong&gt; returns to the client and is decrypted there.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Finally, the client signs the &lt;strong&gt;IntermediaryKey&lt;/strong&gt; with &lt;strong&gt;ClientSecretKey&lt;/strong&gt;, resulting in the &lt;strong&gt;WalletPrivateKey&lt;/strong&gt; (the actual private key of the wallet).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This key is used to sign the transaction and is never saved anywhere long term. For each new action, that chain of steps (except step 1) is repeated.&lt;/p&gt;

&lt;p&gt;In the end, the app’s UX looks perfect: login is seamless thanks to auto-auth in mini apps, and transactions are seamless because there’s an in-app wallet.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Add It to Your Mini App?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install the SDK&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save&lt;/span&gt; @tmawallet/sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Initialize the key in your code&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;   &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;TMAWalletClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tmawallet/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ethers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

   &lt;span class="c1"&gt;// Don't forget to sign up at dash.tmawallet.com&lt;/span&gt;
   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myApiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1234567812345678&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Your API key&lt;/span&gt;
   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TMAWalletClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myApiKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

   &lt;span class="c1"&gt;// Authorize the user and create/load their wallet&lt;/span&gt;
   &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

   &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Your wallet address: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;walletAddress&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Example of making a transaction&lt;/strong&gt; (here using Ethers.js):
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;   &lt;span class="c1"&gt;// Use TMA Wallet as the "signer" for ethers&lt;/span&gt;
   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;JsonRpcProvider&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;signer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getEthersSigner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;signer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendTransaction&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
     &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0x...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parseEther&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;});&lt;/span&gt;
   &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Transaction hash:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that’s it.&lt;/p&gt;




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

&lt;p&gt;Below are questions (slightly edited) from TMA Wallet’s README, with their answers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is this definitely secure?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Yes, that’s the core idea. Thanks to the MPC protocol, neither TMA Wallet’s servers, Telegram, nor you have full access to the private key—only the user does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I have to give you access to my bot’s token?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
No. We’re one of the first to support Telegram’s new asymmetric signature scheme. We only need your bot’s ID, which is already public.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which blockchain can be supported?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Any. EVM blockchains (Ethereum, etc.) work out of the box with ethers.js. For something custom, you can use the &lt;code&gt;accessPrivateKey&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if the user loses their device?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
As long as they have access to their Telegram account, they just log in on a new device, and the wallet is restored automatically. No seed phrase is required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I back up the key?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Technically yes, but you probably don’t need to. The wallet can already be restored through Telegram. If you want, you can let the user back it up, but that’s at your own risk.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We used &lt;strong&gt;TMA Wallet&lt;/strong&gt; in two of our own apps. One is already in production (I was a bit shy to post the link at the start, but I think it’s okay to mention here in the footer: &lt;a href="https://t.me/Only100xBot/app" rel="noopener noreferrer"&gt;Only100x&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;It’s a great option for anyone building Telegram mini apps who wants to give users a secure wallet without messing up the UX with external connectors.&lt;/p&gt;

&lt;p&gt;Feel free to try it and explore the documentation. All the project’s code is &lt;a href="https://github.com/tmawallet/sdk" rel="noopener noreferrer"&gt;open on GitHub&lt;/a&gt;. Good luck!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;telegram mini app&lt;/code&gt; · &lt;code&gt;crypto&lt;/code&gt; · &lt;code&gt;non-custodial wallet&lt;/code&gt; · &lt;code&gt;tma wallet&lt;/code&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>cryptocurrency</category>
      <category>web3</category>
      <category>tma</category>
    </item>
    <item>
      <title>pg_auto_embeddings — text embeddings directly in Postgres, without extensions</title>
      <dc:creator>Daniel S</dc:creator>
      <pubDate>Fri, 10 Jan 2025 12:17:01 +0000</pubDate>
      <link>https://forem.com/elkornacio/pgautoembeddings-text-embeddings-directly-in-postgres-without-extensions-fbd</link>
      <guid>https://forem.com/elkornacio/pgautoembeddings-text-embeddings-directly-in-postgres-without-extensions-fbd</guid>
      <description>&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%2Fd8ni9hyxtt4l8hebby13.jpg" 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%2Fd8ni9hyxtt4l8hebby13.jpg" alt="Image description" width="800" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;You have a PostgreSQL database that stores a lot of text data. You want to use vector representations (embeddings), for example from OpenAI, to build a recommendation system, improved search, or implement RAG for working with LLMs. But you don’t want to install extensions (or maybe you can’t). For instance, on cloud Managed PostgreSQL, you often don’t have the required permissions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/ElKornacio/pg_auto_embeddings" rel="noopener noreferrer"&gt;&lt;strong&gt;pg_auto_embeddings&lt;/strong&gt;&lt;/a&gt; is a lightweight open-source solution that lets you compute embeddings through OpenAI models directly in PostgreSQL without installing external extensions. It uses the Foreign Data Wrappers (FDW) mechanism “under the hood” to send requests to the OpenAI API, and it works synchronously and atomically. In this article, we'll see how &lt;code&gt;pg_auto_embeddings&lt;/code&gt; can help, how to install it (spoiler: very easily), and what the key features of the project are.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is pg_auto_embeddings and what problem does it solve?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/ElKornacio/pg_auto_embeddings" rel="noopener noreferrer"&gt;&lt;strong&gt;pg_auto_embeddings&lt;/strong&gt;&lt;/a&gt; is an MIT-licensed open-source project that solves the key problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How do we compute text vector representations (embeddings) directly from PostgreSQL without extra fuss and without special extensions?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Main ideas:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Call via SQL&lt;/strong&gt;: You write a simple function &lt;code&gt;pgae_embedding('some text')&lt;/code&gt; and that's enough. You can use the function in triggers so that embeddings for text columns are saved automatically.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexible settings&lt;/strong&gt;: You can use public models (for example, OpenAI) or set up an on-premise proxy if you need more control—like adding your own limits, private access, etc.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because of this, &lt;code&gt;pg_auto_embeddings&lt;/code&gt; is great when you need to quickly “plug in” the calculation of embeddings into an existing DB without dealing with external binary extensions. It's convenient for RAG systems and other tasks where embeddings are a core functionality.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No extensions&lt;/strong&gt;: You don't need to install any extra software in PostgreSQL—just run one SQL file (yes, we're repeating it, but it’s important!).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Two deployment options&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Simplified installation&lt;/strong&gt;: Run one SQL script and you’re done.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On-Premise (via Docker)&lt;/strong&gt;: Spin up your own proxy server that handles embedding API requests. Also launched with a single &lt;code&gt;docker run&lt;/code&gt; command.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;OpenAI Embeddings support&lt;/strong&gt;: At the moment, OpenAI models (text-embedding-3-small/large and some others) work out of the box.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Automatic vector update on insert or update of text data&lt;/strong&gt;: You can “attach” auto-embedding to a table column so you don’t have to spend time writing a trigger.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cleanup&lt;/strong&gt;: If needed, you can completely remove pg_auto_embeddings and all of its objects with a single function.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1. Installation
&lt;/h2&gt;

&lt;p&gt;Take the file &lt;a href="https://github.com/ElKornacio/pg_auto_embeddings/blob/main/simple/pgae_simple_install.sql" rel="noopener noreferrer"&gt;&lt;code&gt;simple/pgae_simple_install.sql&lt;/code&gt;&lt;/a&gt; and run it in your database.&lt;/p&gt;

&lt;p&gt;Initialize &lt;code&gt;pg_auto_embeddings&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;pgae_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'openai-text-embedding-3-small'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'YOUR_OPENAI_API_KEY'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it!&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2. Usage
&lt;/h2&gt;

&lt;p&gt;To get a vector (an array of &lt;code&gt;double precision[]&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pgae_embedding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'your text'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you have &lt;code&gt;pgvector&lt;/code&gt; installed and want the &lt;code&gt;vector&lt;/code&gt; format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pgae_embedding_vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'some text'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Automatic calculation and saving of the embedding
&lt;/h3&gt;

&lt;p&gt;Suppose you have a &lt;code&gt;posts&lt;/code&gt; table with a &lt;code&gt;title&lt;/code&gt; column, and you want to automatically store embeddings for titles in the &lt;code&gt;title_embedding&lt;/code&gt; column:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pgae_create_auto_embedding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s1"&gt;'public'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'posts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'title_embedding'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Voila.&lt;/p&gt;

&lt;p&gt;If you decide to remove &lt;code&gt;pg_auto_embeddings&lt;/code&gt; completely along with all its functions and objects, just run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pgae_self_destroy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  [Optional] On-premise option (via Docker)
&lt;/h2&gt;

&lt;p&gt;If you have restrictions on external requests or you want to set up your own proxy server for extra security, it’s easy to do.&lt;/p&gt;

&lt;p&gt;You need to run two services:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A Postgres database that acts as a proxy between FDW and Node.js.&lt;/li&gt;
&lt;li&gt;A Node.js service that sends requests to the model’s API.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;pg_auto_embeddings&lt;/code&gt; provides a Docker image in which both components are already deployed and configured for use.&lt;/p&gt;

&lt;p&gt;Below is an example &lt;code&gt;docker-compose.yml&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;elkornacio/pg_auto_embeddings:latest&lt;/span&gt;
        &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PG_HOST=localhost&lt;/span&gt;  &lt;span class="c1"&gt;# Host of the proxying Postgres&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PG_PORT=5432&lt;/span&gt;       &lt;span class="c1"&gt;# Its port&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PG_USERNAME=root_user&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PG_PASSWORD=root_pass&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_SYNC=true&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SERVER_HOST=localhost&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SERVER_PORT=3000&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SELF_URL=http://localhost:3000&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# The port of the proxying Postgres must be open — your managed DB will connect to it&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;5432:5432&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, instead of &lt;code&gt;pgae_init&lt;/code&gt;, you use &lt;code&gt;pgae_init_onprem&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;pgae_init_onprem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s1"&gt;'your.host.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'5432'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;-- host and port of your proxying Postgres&lt;/span&gt;
  &lt;span class="s1"&gt;'openai-text-embedding-3-small'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'sk-...'&lt;/span&gt;  &lt;span class="c1"&gt;-- model type and API key&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usage is the same as with the "simple" option:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pgae_embedding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'your text'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  How does it work inside?
&lt;/h2&gt;

&lt;p&gt;Under the hood, &lt;code&gt;pg_auto_embeddings&lt;/code&gt; uses a trick based on Foreign Data Wrappers (FDW):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When you install it, a “proxy table” named &lt;code&gt;embeddings_*&lt;/code&gt; is created in your local database.&lt;/li&gt;
&lt;li&gt;When you call &lt;code&gt;SELECT pgae_embedding('some text')&lt;/code&gt;, internally there’s an &lt;code&gt;UPDATE&lt;/code&gt; to this “proxy table,” passing the text.&lt;/li&gt;
&lt;li&gt;The FDW redirects the request to a remote table (in the Docker proxy or a public server).&lt;/li&gt;
&lt;li&gt;On the remote server, a trigger fires that calls the internal function &lt;code&gt;pgae_embedding_internal()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;This function sends an HTTP request to the Node.js proxy.&lt;/li&gt;
&lt;li&gt;The Node.js server calls the OpenAI API (or another provider if you’re using something else) and gets a vector.&lt;/li&gt;
&lt;li&gt;The vector returns to the remote DB, then back to the local DB, and finally shows up in your &lt;code&gt;SELECT&lt;/code&gt; query.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;pg_auto_embeddings&lt;/strong&gt; is perfect when you need a quick and simple way to compute vector representations. It’s ideal for those who want to connect their DB with an LLM or build advanced full-text search directly in SQL.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/ElKornacio/pg_auto_embeddings" rel="noopener noreferrer"&gt;The project is active&lt;/a&gt; and open to suggestions and stars :) PRs are also welcome. If you have any questions, feel free to post them in GitHub Issues.&lt;/p&gt;

&lt;p&gt;Thanks for reading and happy experimenting :) If you find any mistakes in the text, please message me, and I’ll fix them quickly.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags&lt;/strong&gt;: embeddings, rag, postgresql, postgres, embeddings, vector representations, ai &lt;/p&gt;

</description>
      <category>postgres</category>
      <category>rag</category>
      <category>ai</category>
      <category>vectordatabase</category>
    </item>
  </channel>
</rss>
