<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[AdamPay Blog]]></title><description><![CDATA[AdamPay Blog]]></description><link>https://adampay.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Fri, 30 Oct 2026 03:45:28 GMT</lastBuildDate><atom:link href="https://adampay.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Building a non-custodial payment app on Solana: lessons learned]]></title><description><![CDATA[AdamPay is a free web app that turns SOL into something you can spend. You deposit SOL, convert it to USDC through Jupiter, and pay with Solana Pay QR…]]></description><link>https://adampay.hashnode.dev/building-a-non-custodial-payment-app-on-solana-lessons-learned</link><guid isPermaLink="true">https://adampay.hashnode.dev/building-a-non-custodial-payment-app-on-solana-lessons-learned</guid><category><![CDATA[Solana]]></category><category><![CDATA[Web3]]></category><category><![CDATA[PWA]]></category><category><![CDATA[Security]]></category><category><![CDATA[UX]]></category><dc:creator><![CDATA[AdamPay]]></dc:creator><pubDate>Mon, 26 Oct 2026 17:06:07 GMT</pubDate><content:encoded><![CDATA[<p><a href="https://adampay.app">AdamPay</a> is a free web app that turns SOL into something you can spend. You deposit SOL, convert it to USDC through Jupiter, and pay with Solana Pay QR codes, while your funds stay in your own Phantom wallet. This post covers the decisions behind it, not the code.</p>
<h2>Lesson 1: Decide what you will <em>never</em> do, first</h2>
<p>The first design decision wasn't a feature. It was a list of things the app would never do:</p>
<ul>
<li>never hold user funds,</li>
<li>never see or store private keys or seed phrases,</li>
<li>never sign anything on the user's behalf,</li>
<li>never swap automatically.</li>
</ul>
<p>Everything else follows from that list. Every action that moves money, whether a swap or a payment, is built in the browser and handed to Phantom with <code>signAndSendTransaction</code>. If the user doesn't approve, nothing happens. Even the "auto-convert" option is only a prompt: when SOL crosses a threshold you choose, the app <em>asks</em> whether you want to review a conversion.</p>
<p>The security model is easy to explain: an AdamPay account (email + password) gets you into the app, but it can't move your money. Compromising the account doesn't compromise the wallet.</p>
<h2>Lesson 2: Keep the trusted surface small</h2>
<p>AdamPay is static files on a CDN. Solana data comes from public RPC endpoints, swap routes come from Jupiter, and accounts come from Supabase Auth with only the browser-safe publishable key shipped to the client. There's no AdamPay server in the money path.</p>
<p>The service worker follows the same principle. It caches only the app's own shell files, and it never intercepts cross-origin requests like RPC, Jupiter or auth. Caching a stale price or a stale transaction would be a real bug in a payment app, so the worker is network-first and uses the cache only as an offline fallback.</p>
<h2>Lesson 3: Simple UI, honest numbers</h2>
<p>The aim was a simple feel: one balance, then Deposit, Convert, Pay, in a midnight-and-electric-blue theme. But "simple" can't mean hiding costs. So the convert screen shows the expected USDC, the <strong>minimum received</strong> at 0.5% slippage, the price impact (red above 1%), and Jupiter's route. It also reserves 0.01 SOL so "Max" never leaves the wallet unable to pay fees.</p>
<p>AdamPay adds no fee of its own. Network fees and market pricing still apply, and the UI says so rather than claiming "free payments".</p>
<p>The pay confirmation shows the recipient, amount and network fee, plus the ~0.002 SOL one-time cost when the recipient needs a new USDC account. It also reminds you that Solana payments can't be reversed.</p>
<h2>Lesson 4: Validate before you sign, not after</h2>
<p>Most painful crypto mistakes happen before the signature. So AdamPay checks the recipient on-chain <em>before</em> enabling the Continue button:</p>
<ul>
<li>Is it a USDC token account, or a wallet?</li>
<li>Does the wallet already have a USDC account, or does one need to be created?</li>
<li>Is it an off-curve (program) address? If so, the app shows a warning.</li>
<li>Does the Solana Pay request ask for a token the app doesn't pay in? If so, it says that clearly instead of failing later.</li>
</ul>
<p>A clear message up front beats a failed transaction every time.</p>
<h2>Lesson 5: "Download the app" on iOS is a UX problem, not a code problem</h2>
<p>Making the PWA installable was the easy part: a manifest, icons and a service worker. Getting real people to <em>install</em> it was harder.</p>
<ul>
<li><strong>Android/Chrome</strong> fires <code>beforeinstallprompt</code>, so a <em>Download AdamPay</em> button can open the native install dialog.</li>
<li><strong>iOS</strong> has no install prompt at all. The only path is Safari → <em>Share</em> → <em>Add to Home Screen</em>, so the app shows those steps.</li>
<li><strong>In-app browsers</strong> were the real surprise. Most people open links from X, Telegram or Instagram, and those built-in browsers can't install a PWA. The button seemed broken, when really the browser couldn't do it. The fix was detecting in-app browsers and showing "Open in Safari/Chrome", plus a copy-link fallback for when that doesn't work.</li>
</ul>
<p>The takeaway: test your install flow from wherever your links will actually be opened, not just from a desktop browser.</p>
<h2>Lesson 6: Accounts are a product decision, too</h2>
<p>Requiring an account adds friction, but it gives a clear home for sessions and future features. The landing page stays public; the app sits behind login. Email + password came first, and "Log in with Phantom" is coming soon.</p>
<h2>A note on risk</h2>
<p>Crypto prices are volatile. With a non-custodial app you are responsible for your wallet and seed phrase, and nobody can reverse a payment or recover a lost seed. Not financial advice.</p>
<h2>Try it</h2>
<p>AdamPay is live at <strong><a href="https://adampay.app">adampay.app</a></strong>. Follow <strong><a href="https://x.com/adampayapp">@adampayapp</a></strong> on X for updates. Feedback on the security model or the install flow is very welcome.</p>
]]></content:encoded></item></channel></rss>