<?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: Ubong Ntekim</title>
    <description>The latest articles on DEV Community by Ubong Ntekim (@ubongn).</description>
    <link>https://dev.to/ubongn</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%2F518813%2F37410f0d-d362-4d62-88df-b257677d65af.jpeg</url>
      <title>DEV Community: Ubong Ntekim</title>
      <link>https://dev.to/ubongn</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ubongn"/>
    <language>en</language>
    <item>
      <title>Gasless on Soroban — How We Made Joining an On-Chain Savings Circle Cost Nothing</title>
      <dc:creator>Ubong Ntekim</dc:creator>
      <pubDate>Thu, 10 Sep 2026 11:17:22 +0000</pubDate>
      <link>https://dev.to/ubongn/gasless-on-soroban-how-we-made-joining-an-on-chain-savings-circle-cost-nothing-1oi1</link>
      <guid>https://dev.to/ubongn/gasless-on-soroban-how-we-made-joining-an-on-chain-savings-circle-cost-nothing-1oi1</guid>
      <description>&lt;p&gt;&lt;em&gt;A technical walkthrough of fee-bump sponsorship for Soroban dApps — shipped in production on &lt;a href="https://stellar-nova-esusu.vercel.app" rel="noopener noreferrer"&gt;Nova Esusu&lt;/a&gt;, a rotating savings circles app on Stellar.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;If you're building a Soroban dApp for people who have never held crypto, you will hit the same wall I did: &lt;strong&gt;the first transaction is the hardest one.&lt;/strong&gt; A new user has to acquire XLM, create an account, pay a base reserve, and fund a transaction fee — all before they experience a single feature of your product.&lt;/p&gt;

&lt;p&gt;For my first 51 testnet testers this was friction-free (thanks, friendbot). But mainnet has no faucet. So before launching &lt;a href="https://stellar-nova-esusu.vercel.app" rel="noopener noreferrer"&gt;Nova Esusu&lt;/a&gt; — on-chain rotating savings circles (the West African &lt;em&gt;Esusu&lt;/em&gt; tradition) — on mainnet, I needed an answer to one question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Can a new user join a savings circle without ever owning a fee?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer is yes, and it's built into Stellar itself: &lt;strong&gt;fee-bump transactions&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern: user signs, sponsor pays
&lt;/h2&gt;

&lt;p&gt;Stellar's transaction model separates two things that most chains conflate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;source&lt;/code&gt;&lt;/strong&gt; — the account whose sequence number the transaction consumes, and whose signature authorizes the operations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;fee source&lt;/strong&gt; — the account that pays the network fee&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A fee-bump envelope (&lt;code&gt;ENVELOPE_TYPE_TX_FEE_BUMP&lt;/code&gt;) wraps a fully-signed inner transaction with a second payer:&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;feeBump&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TransactionBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buildFeeBumpTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;sponsorKeypair&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publicKey&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;        &lt;span class="c1"&gt;// feeSource — pays everything&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;innerFee&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;         &lt;span class="c1"&gt;// declared fee, capped&lt;/span&gt;
  &lt;span class="nx"&gt;innerTransaction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                  &lt;span class="c1"&gt;// signed by the user only&lt;/span&gt;
  &lt;span class="nx"&gt;networkPassphrase&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;feeBump&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sponsorKeypair&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The magic: the user's signature only ever authorizes the &lt;em&gt;inner&lt;/em&gt; operations. The sponsor's signature only authorizes &lt;em&gt;paying&lt;/em&gt;. Neither can spend the other's funds. This is the safest possible trust boundary — the sponsor is not a custodian, not a co-signer, and cannot move the user's money.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser                      Serverless API                 Stellar
   |                              |                           |
   | 1. sign inner tx (user)      |                           |
   |-------------------------------&amp;gt;                           |
   | 2. POST { xdr, network }     |                           |
   |-----------------------------&amp;gt;|                           |
   |                              | 3. validate rails         |
   |                              | 4. wrap + sign fee-bump   |
   |                              | 5. submit -----------------&amp;gt;|
   |                              |                           |
   | &amp;lt;------ 6. { hash } ---------|---------- chain ---------- |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three pieces, ~150 lines total:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The shared builder&lt;/strong&gt; (&lt;code&gt;shared/feebump.ts&lt;/code&gt;) — wraps any user-signed XDR in a fee-bump envelope. This module typechecks under both the Vite toolchain and the serverless toolchain, which matters more than it sounds (ask me how I know).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The sponsor endpoint&lt;/strong&gt; (&lt;code&gt;api/sponsor.ts&lt;/code&gt;, Vercel serverless) — receives the signed inner transaction, validates it, wraps, signs, submits. When the sponsor key is absent it returns &lt;code&gt;503&lt;/code&gt;, and the frontend &lt;strong&gt;silently falls back to normal submission&lt;/strong&gt; — the user never sees an error, the feature just gracefully disappears.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The frontend opt-in&lt;/strong&gt; — a "Gasless" checkbox on the join/create flows. Opt-in, never default-hidden, so reviewers can see exactly what's happening.&lt;/p&gt;

&lt;h2&gt;
  
  
  The abuse rails (read this before you ship one)
&lt;/h2&gt;

&lt;p&gt;An open "someone else pays my fees" endpoint is a generous gift to the internet. Mine has four rails, all enforced server-side:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fee cap&lt;/strong&gt; — the inner transaction's &lt;em&gt;declared&lt;/em&gt; fee is capped (5 XLM). A malicious client can't submit &lt;code&gt;MAX_INT&lt;/code&gt; and drain the sponsor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sponsor ≠ source&lt;/strong&gt; — the endpoint refuses to sponsor a transaction whose inner source is the sponsor itself (prevents using the endpoint as a weird signing oracle).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Already-bumped envelopes rejected&lt;/strong&gt; — only raw inner transactions are accepted, one wrap only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network pinned per environment&lt;/strong&gt; — &lt;code&gt;testnet&lt;/code&gt; and &lt;code&gt;mainnet&lt;/code&gt; sponsor keys are separate env vars; a mainnet request can never be signed by the testnet wallet.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The cap matters most. Soroban fees are based on declared resource limits — an adversarial client could declare enormous instructions. Cap early, cap server-side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proof it works: a fresh user, zero fees, on-chain
&lt;/h2&gt;

&lt;p&gt;I verified the full path in production: created a brand-new keypair, funded it with the minimum, had it sign a &lt;code&gt;create_circle&lt;/code&gt; call, POSTed to the live endpoint, and confirmed on-chain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transaction: &lt;code&gt;912b64619d39e2f6800894685a37adee77711bb5f58af1957da388df1b24b94a&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Envelope type: &lt;code&gt;ENVELOPE_TYPE_TX_FEE_BUMP&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Fee source: the sponsor wallet&lt;/li&gt;
&lt;li&gt;The user account's balance: unchanged except for the operation itself&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Things that cost me an afternoon (so they don't cost you one)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The SDK wants class instances.&lt;/strong&gt; &lt;code&gt;server.sendTransaction()&lt;/code&gt; accepts &lt;code&gt;Transaction | FeeBumpTransaction&lt;/code&gt;, not a raw XDR object — a base64 string serialized as an object gets rejected by the RPC with a wonderfully unhelpful &lt;code&gt;invalid parameters&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vercel functions are Express-style.&lt;/strong&gt; A web-standard &lt;code&gt;Request/Response&lt;/code&gt; handler doesn't hang on import — it hangs on &lt;em&gt;invocation&lt;/em&gt;: the runtime waits for &lt;code&gt;res.end()&lt;/code&gt; forever and you get &lt;code&gt;FUNCTION_INVOCATION_TIMEOUT&lt;/code&gt;. Write &lt;code&gt;(req, res)&lt;/code&gt; handlers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Soroban rejects classic operations.&lt;/strong&gt; &lt;code&gt;bumpSequence&lt;/code&gt;, &lt;code&gt;manageData&lt;/code&gt; et al. fail simulation (&lt;code&gt;unsupported operation type&lt;/code&gt;). Your inner transaction must be contract calls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fee-bump signing order is irrelevant but presence isn't.&lt;/strong&gt; The inner signature must exist &lt;em&gt;before&lt;/em&gt; wrapping — you're signing the hash of the inner tx, and the sponsor signs the hash of the wrapped envelope.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why this matters for adoption
&lt;/h2&gt;

&lt;p&gt;Every chain ecosystem says it wants "the next billion users." Most wallets in the markets that actually need savings circles (West Africa, Southeast Asia, Latin America) start empty. Fee sponsorship turns a four-step onboarding (get an exchange account → buy XLM → withdraw → fund transactions) into a one-step one (connect wallet, join).&lt;/p&gt;

&lt;p&gt;The Esusu circle my testers joined cost them exactly zero in fees on testnet, and on mainnet the sponsor pool will carry the fees for the first wave of members. &lt;strong&gt;The people who need savings infrastructure the most are the ones least able to pay friction costs to try it.&lt;/strong&gt; Fee sponsorship is how a protocol says "the first round is on me."&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Nova Esusu is open source: &lt;a href="https://github.com/ubongn/stellar-nova-esusu" rel="noopener noreferrer"&gt;github.com/ubongn/stellar-nova-esusu&lt;/a&gt;. Contracts: SavingsPool (escrow + payouts) and MemberManager (reputation + eligibility), with the payout order locked on-chain at circle activation. Questions or feedback — find me on X &lt;a href="https://x.com/ubongn" rel="noopener noreferrer"&gt;@ubongn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>stellar</category>
      <category>soroban</category>
      <category>blockchain</category>
      <category>web3</category>
    </item>
  </channel>
</rss>
