<?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: Tunmise Ola</title>
    <description>The latest articles on DEV Community by Tunmise Ola (@tunmi_stom).</description>
    <link>https://dev.to/tunmi_stom</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%2F3928028%2Fdd981ada-5795-4393-92d8-e59f565ddca3.png</url>
      <title>DEV Community: Tunmise Ola</title>
      <link>https://dev.to/tunmi_stom</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tunmi_stom"/>
    <language>en</language>
    <item>
      <title>Building a Token &amp; NFT Portfolio API for Ethereum with FastAPI and Web3</title>
      <dc:creator>Tunmise Ola</dc:creator>
      <pubDate>Thu, 28 May 2026 22:57:14 +0000</pubDate>
      <link>https://dev.to/tunmi_stom/building-a-token-nft-portfolio-api-for-ethereum-with-fastapi-and-web3-508a</link>
      <guid>https://dev.to/tunmi_stom/building-a-token-nft-portfolio-api-for-ethereum-with-fastapi-and-web3-508a</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Ethereum wallets can hold ETH, dozens of ERC-20 tokens, and NFTs spread across multiple collections — yet most tools only show you one piece at a time. I set out to fix that by building a &lt;strong&gt;Token &amp;amp; NFT Portfolio API&lt;/strong&gt;: a FastAPI service that aggregates Ethereum wallet balances, fungible tokens, and NFTs into clean, structured JSON responses.&lt;/p&gt;

&lt;p&gt;Whether you're building a crypto dashboard, an NFT explorer, a wallet intelligence tool, or an Ethereum analytics platform, this API gives you a single endpoint to query instead of cobbling together multiple providers.&lt;/p&gt;

&lt;p&gt;Here's what we built, how it works, and what comes next.&lt;/p&gt;




&lt;h2&gt;
  
  
  What It Does
&lt;/h2&gt;

&lt;p&gt;The API solves a deceptively simple problem: given a wallet address, return everything that wallet holds — tokens, NFTs, and native balances — in a consistent, developer-friendly format.&lt;/p&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ethereum wallet portfolio tracking&lt;/strong&gt; — query token and NFT holdings for any wallet address&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NFT portfolio retrieval&lt;/strong&gt; — surface NFT holdings by collection, token ID, and metadata&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fungible token balance aggregation&lt;/strong&gt; — aggregate ERC-20 balances for a given wallet&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean JSON responses&lt;/strong&gt; — typed, predictable response shapes that frontend apps can trust&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-generated API docs&lt;/strong&gt; — Swagger UI and ReDoc ship out of the box with FastAPI&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Framework&lt;/td&gt;
&lt;td&gt;FastAPI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server&lt;/td&gt;
&lt;td&gt;Uvicorn&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blockchain data&lt;/td&gt;
&lt;td&gt;Web3 APIs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP client&lt;/td&gt;
&lt;td&gt;httpx (async)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data modeling&lt;/td&gt;
&lt;td&gt;Pydantic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth tokens&lt;/td&gt;
&lt;td&gt;python-jose&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Config&lt;/td&gt;
&lt;td&gt;python-dotenv&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wallet utilities&lt;/td&gt;
&lt;td&gt;web3, eth-account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database (future)&lt;/td&gt;
&lt;td&gt;SQLAlchemy + Alembic + PostgreSQL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;

&lt;p&gt;The codebase follows a clean separation of concerns — routes own HTTP logic, services own business logic, schemas own data shapes, and utils own helpers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;token-nft_portfolio_api/
│
├── app/
│   ├── main.py     
│   ├── routes/        
│   ├── services/      
│   ├── schemas/         
│   └── utils/           
│
├── requirements.txt
├── .env                 # API keys (never committed)
├── .gitignore
└── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This structure scales well. Adding a new feature area means dropping a new service module and wiring up a route — the rest of the app stays untouched.&lt;/p&gt;




&lt;h2&gt;
  
  
  API Endpoints
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;GET /portfolio/summary/{wallet_address}&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Returns a complete Ethereum portfolio overview for the given wallet — ETH balance, ERC-20 token holdings, and NFTs.&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;"wallet"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0x123..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"chain"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ethereum"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tokens"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ETH"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"balance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.25"&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;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nfts"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CryptoPunk"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"token_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1234"&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;span class="p"&gt;]&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;h3&gt;
  
  
  &lt;code&gt;GET /portfolio/nft/{wallet_address}&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Fetches the full NFT holdings for a wallet: collection name, token ID, and available metadata. Useful for building NFT gallery views or ownership verification flows.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;GET /wallet/balance/{wallet_address}&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Returns the native ETH balance for the specified wallet address. Lightweight endpoint for quick balance checks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Clone and install:&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;git clone https://github.com/tunmi-stom/token-nft_portfolio_api.git
&lt;span class="nb"&gt;cd &lt;/span&gt;token-nft_portfolio_api

python &lt;span class="nt"&gt;-m&lt;/span&gt; venv venv
&lt;span class="nb"&gt;source &lt;/span&gt;venv/bin/activate        &lt;span class="c"&gt;# Linux/macOS&lt;/span&gt;
&lt;span class="c"&gt;# venv\Scripts\activate         # Windows&lt;/span&gt;

pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Configure your environment:&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;&lt;span class="c"&gt;# .env&lt;/span&gt;
&lt;span class="nv"&gt;MORALIS_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_api_key_here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Start the server:&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;uvicorn app.main:app &lt;span class="nt"&gt;--reload&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API will be live at &lt;code&gt;http://127.0.0.1:8000&lt;/code&gt;. Hit &lt;code&gt;/docs&lt;/code&gt; for the interactive Swagger UI, or &lt;code&gt;/redoc&lt;/code&gt; for the ReDoc interface.&lt;/p&gt;




&lt;h2&gt;
  
  
  Design Decisions Worth Talking About
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Why async?&lt;/strong&gt;&lt;br&gt;
Blockchain data fetching is I/O-bound. When a single portfolio request might fan out into three separate API calls (native balance, ERC-20s, NFTs), running them sequentially is wasteful. FastAPI's async support with &lt;code&gt;httpx&lt;/code&gt; lets those calls happen concurrently, cutting response times significantly.&lt;/p&gt;




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

&lt;p&gt;This API is intentionally generic at its core, but the endpoints map directly to real product surfaces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NFT portfolio dashboards&lt;/strong&gt; — display a collector's holdings with live metadata&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wallet explorers&lt;/strong&gt; — let users paste an address and see everything it holds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blockchain analytics platforms&lt;/strong&gt; — feed aggregated token data into charts and reports&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crypto accounting tools&lt;/strong&gt; — pull balances for tax lot tracking or portfolio valuation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web3 portfolio managers&lt;/strong&gt; — show users their Ethereum portfolio value in one view&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What's Coming Next
&lt;/h2&gt;

&lt;p&gt;The current version is a solid read-only data layer. Here's where it goes from here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Transaction history&lt;/strong&gt; — full inbound/outbound history per wallet&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-wallet support&lt;/strong&gt; — aggregate across multiple addresses in a single request&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;USD portfolio valuation&lt;/strong&gt; — attach live price feeds to compute total value&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching layer&lt;/strong&gt; — Redis-backed TTL caching to reduce Moralis API calls under load&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication &amp;amp; rate limiting&lt;/strong&gt; — per-API-key auth and usage throttling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solana support&lt;/strong&gt; — extend the service layer to SPL tokens and Metaplex NFTs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WebSocket live updates&lt;/strong&gt; — push balance changes to connected clients in real time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker deployment&lt;/strong&gt; — containerized setup for consistent prod/dev environments&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;This project started as a personal tool and grew into something I wanted other developers to be able to use. Querying Ethereum data cleanly — across ETH balances, ERC-20 tokens, and NFTs — is more work than it looks. FastAPI makes it manageable, and Pydantic keeps the response shapes honest.&lt;/p&gt;

&lt;p&gt;The full source is on GitHub: &lt;a href="https://github.com/tunmi-stom/token-nft_portfolio_api" rel="noopener noreferrer"&gt;github.com/tunmi-stom/token-nft_portfolio_api&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Contributions are welcome. Fork it, open an issue, or build something with it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built by Tunmise Ola.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>web3</category>
      <category>fastapi</category>
      <category>backend</category>
    </item>
    <item>
      <title>How to Create Custom API Documentation in FastAPI</title>
      <dc:creator>Tunmise Ola</dc:creator>
      <pubDate>Wed, 20 May 2026 09:16:18 +0000</pubDate>
      <link>https://dev.to/tunmi_stom/how-to-create-custom-api-documentation-in-fastapi-mdb</link>
      <guid>https://dev.to/tunmi_stom/how-to-create-custom-api-documentation-in-fastapi-mdb</guid>
      <description>&lt;p&gt;Have you ever wondered if you could actually change or edit your &lt;code&gt;/docs&lt;/code&gt; or &lt;code&gt;/redoc&lt;/code&gt; API documentation page? Or are you tired of seeing the same docs page every time you develop an API? Well, you guessed right if you thought it was possible, because yes, it is.&lt;br&gt;
In this article, discover how to develop and customize your own docs page.&lt;/p&gt;

&lt;p&gt;Table of Content:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;What FastAPI Gives You By Default&lt;/li&gt;
&lt;li&gt;Changing the Documetation URLs&lt;/li&gt;
&lt;li&gt;Disabling Default Docs&lt;/li&gt;
&lt;li&gt;Creating your own Docs Page&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;API Documentation is a feature of an API that gives an overview of an API. That is, its endpoints, the API's current version, etc. FastAPI Documentation is a built-in feature of FastAPI that allows users to have an overview of the capabilities and information about the API, without having to create the API Documentation.&lt;/p&gt;

&lt;p&gt;API documentation is an important part of every API because it provides developers with an overview of how the API works, including its available endpoints, request and response formats, authentication methods, current version, and other important details.&lt;/p&gt;

&lt;p&gt;One of the reasons many developers love FastAPI is its built-in documentation system. Unlike many backend frameworks where developers need to manually create and maintain documentation, FastAPI automatically generates interactive API documentation out of the box.&lt;/p&gt;

&lt;p&gt;With FastAPI, developers can easily test endpoints, inspect request schemas, and understand the capabilities of an API directly from the browser. Even better, FastAPI also allows you to customize these documentation pages to fit your branding, security needs, or developer experience goals.&lt;/p&gt;

&lt;p&gt;In this article, we will explore how FastAPI documentation works and how you can create fully custom documentation pages for your APIs.&lt;/p&gt;
&lt;h1&gt;
  
  
  Prerequisites
&lt;/h1&gt;

&lt;p&gt;To follow along with this article, you should have foreknowledge of the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Basic Frontend Programming (HTML, CSS &amp;amp; JS).&lt;/li&gt;
&lt;li&gt;Basic Python syntax knowledge.&lt;/li&gt;
&lt;li&gt;A General understanding of how APIs work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Python Packages to install:&lt;br&gt;
FastAPI - The framework to help us create the API.&lt;br&gt;
Uvicorn - A lightning-fast ASGI server to run the FastAPI App.&lt;/p&gt;

&lt;p&gt;Run in in your command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;fastapi uvicorn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  What FastAPI Gives You By Default
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Create a FastAPI App&lt;/strong&gt;&lt;br&gt;
To create an API, create a Python file &lt;code&gt;main.py&lt;/code&gt;&lt;br&gt;
In this file, type the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;My first API built with FastAPI&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open the file's directory in your command line and type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uvicorn main:app &lt;span class="nt"&gt;--reload&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open your browser and enter &lt;code&gt;http://localhost:8000/&lt;/code&gt; and you will see the API. Like this:&lt;br&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%2Few198cyswkosmu1cike3.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%2Few198cyswkosmu1cike3.png" alt=" " width="800" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then FastAPI generates two docs for you:&lt;br&gt;
Swagger UI: &lt;a href="http://localhost:8000/docs" rel="noopener noreferrer"&gt;http://localhost:8000/docs&lt;/a&gt;&lt;br&gt;
Redocly: &lt;a href="http://localhost:8000/redoc" rel="noopener noreferrer"&gt;http://localhost:8000/redoc&lt;/a&gt;&lt;br&gt;
Your API's default documentation pages will look like these:&lt;br&gt;
&lt;strong&gt;For Swagger:&lt;/strong&gt;&lt;br&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%2Fual8u9sh6l9wieqasqoa.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%2Fual8u9sh6l9wieqasqoa.png" alt=" " width="800" height="460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For Redocly:&lt;/strong&gt;&lt;br&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%2F97fbnwwhsdq8jiq22t7w.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%2F97fbnwwhsdq8jiq22t7w.png" alt=" " width="800" height="524"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Changing the Documentation URLs
&lt;/h1&gt;

&lt;p&gt;One of the things you can customise is the Documentations URLs. You can change the default documentation by changing the parameters of app to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;docs_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/documentation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;redoc_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/reference&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you try to access the &lt;code&gt;/docs&lt;/code&gt; or &lt;code&gt;/redoc&lt;/code&gt; endpoints, it returns Not Found.&lt;/p&gt;

&lt;h1&gt;
  
  
  Disabling Default Docs
&lt;/h1&gt;

&lt;p&gt;You can disable the documentations page by setting the parameters of the app variable to None:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;docs_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;redoc_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Creating your own Docs Page
&lt;/h1&gt;

&lt;p&gt;By default, a FastAPI application returns JSON responses for API endpoints. However, since browsers can also render HTML, you can build a fully custom documentation page by returning HTML instead of JSON for a specific route.&lt;/p&gt;

&lt;p&gt;To do this, you simply create an endpoint that returns HTML content using FastAPI’s &lt;code&gt;HTMLResponse&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can also disable the default Swagger and ReDoc documentation pages if you want full control over your API documentation interface.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example: Custom Documentation Page&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi.responses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HTMLResponse&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;docs_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;redoc_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FastAPI APP&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/docs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;include_in_schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response_class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HTMLResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;custom_docs&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;FastAPI Custom Docs&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;FastAPI Custom Docs&amp;lt;/h1&amp;gt;
    &amp;lt;p&amp;gt;This is a simple custom documentation page.&amp;lt;/p&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The /docs endpoint now returns HTML instead of JSON.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;response_class=HTMLResponse&lt;/code&gt; tells FastAPI to render the response as HTML.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;include_in_schema=False&lt;/code&gt; hides this route from the autogenerated API documentation.&lt;/li&gt;
&lt;li&gt;Setting &lt;code&gt;docs_url=None&lt;/code&gt; disables the default Swagger UI.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;p&gt;When you visit:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;you will see your custom HTML documentation page instead of the default FastAPI Swagger interface.&lt;/p&gt;

&lt;p&gt;From here, you can fully customize the page with:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;CSS styling&lt;/li&gt;
&lt;li&gt;JavaScript interactivity&lt;/li&gt;
&lt;li&gt;Branding and logos&lt;/li&gt;
&lt;li&gt;Links to API guides or SDKs&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;FastAPI gives you a powerful starting point with automatic documentation, but its real strength is flexibility. You can either use the built-in tools or completely replace them with your own custom developer experience.&lt;/p&gt;

&lt;p&gt;If you’re building production-grade APIs, especially something like wallet infrastructure or developer tools, custom documentation is not just a nice-to-have feature — it’s a competitive advantage.&lt;/p&gt;

</description>
      <category>python</category>
      <category>fastapi</category>
      <category>webdev</category>
      <category>api</category>
    </item>
    <item>
      <title>Building Ethereum Wallet Authentication with FastAPI and Web3.py</title>
      <dc:creator>Tunmise Ola</dc:creator>
      <pubDate>Sun, 17 May 2026 21:00:33 +0000</pubDate>
      <link>https://dev.to/tunmi_stom/building-ethereum-wallet-authentication-with-fastapi-and-web3py-182c</link>
      <guid>https://dev.to/tunmi_stom/building-ethereum-wallet-authentication-with-fastapi-and-web3py-182c</guid>
      <description>&lt;p&gt;Structuring and building a web3 wallet authentication is not as hard as it sounds and with the right guidance and attitude, you will find building backend systems for dApps (not just wallet auth) as more of a hobby than a tedious work.&lt;/p&gt;

&lt;p&gt;Table of Content:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Foreknowledge&lt;/li&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;The architecture&lt;/li&gt;
&lt;li&gt;Nonce generation&lt;/li&gt;
&lt;li&gt;Signing Flow&lt;/li&gt;
&lt;li&gt;Verification of the Signature&lt;/li&gt;
&lt;li&gt;JSON Web Token (JWT) Issuance&lt;/li&gt;
&lt;li&gt;Best Practices&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Introduction:&lt;/strong&gt;&lt;br&gt;
In traditional web applications, authentication is usually built around usernames, emails, and passwords. Web3 applications take a completely different approach. Instead of proving identity with a password, users prove ownership of a wallet through cryptographic signatures.&lt;/p&gt;

&lt;p&gt;This is where wallet authentication comes in. Wallet authentication allows users to securely log into decentralized applications (dApps) using wallets like MetaMask, or Trust Wallet without creating traditional accounts or storing passwords.&lt;br&gt;
In this article, we will build a complete Ethereum wallet authentication backend. We will cover the architecture, nonce generation, wallet signing flow, signature verification, and JWT issuance.&lt;br&gt;
By the end of this guide, you will understand how modern dApps authenticate users securely while keeping the backend lightweight and scalable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Foreknowledge:&lt;/strong&gt;&lt;br&gt;
Before you continue with the tutorial for implementation, you should have foreknowledge of these concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Basic Python programming &lt;/li&gt;
&lt;li&gt;APIs and HTTP requests &lt;/li&gt;
&lt;li&gt;REST architecture &lt;/li&gt;
&lt;li&gt;JSON data handling &lt;/li&gt;
&lt;li&gt;Basic understanding of blockchain wallets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You do not need advanced blockchain knowledge to follow through this tutorial. As long as you understand how APIs work, you can build wallet authentication systems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;br&gt;
To follow along, make sure you have the following installed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.10+ &lt;/li&gt;
&lt;li&gt;Pip &lt;/li&gt;
&lt;li&gt;Virtual environment &lt;/li&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You will also need these Python libraries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;fastapi uvicorn web3 eth-account jose python-dotenv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should also have:&lt;/p&gt;

&lt;p&gt;A crypto wallet like MetaMask&lt;br&gt;
Basic understanding of signing messages&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.github.com/tunmi-stom/wallet_auth_api" rel="noopener noreferrer"&gt;You can find the github repo here&lt;/a&gt;&lt;br&gt;
&lt;a href="https://wallet-auth-api.onrender.com/" rel="noopener noreferrer"&gt;You can find the Live API here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Architecture&lt;/strong&gt;&lt;br&gt;
Before writing code, let us understand the authentication flow.&lt;br&gt;
Unlike traditional login systems, wallet authentication does not use passwords. Instead, the backend generates a unique message (nonce), and the wallet signs it.&lt;br&gt;
The flow works like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User connects wallet &lt;/li&gt;
&lt;li&gt;Backend generates nonce &lt;/li&gt;
&lt;li&gt;Frontend asks wallet to sign nonce &lt;/li&gt;
&lt;li&gt;User signs the message &lt;/li&gt;
&lt;li&gt;Signed message is sent to backend &lt;/li&gt;
&lt;li&gt;Backend verifies signature &lt;/li&gt;
&lt;li&gt;Backend issues JWT token &lt;/li&gt;
&lt;li&gt;User becomes authenticated &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A visual representation can be:&lt;br&gt;
Frontend&lt;br&gt;
   ↓&lt;br&gt;
Request Nonce&lt;br&gt;
   ↓&lt;br&gt;
Backend Generates Nonce&lt;br&gt;
   ↓&lt;br&gt;
Wallet Signs Message&lt;br&gt;
   ↓&lt;br&gt;
Frontend Sends Signature&lt;br&gt;
   ↓&lt;br&gt;
Backend Verifies Signature&lt;br&gt;
   ↓&lt;br&gt;
JWT Token Issued&lt;/p&gt;

&lt;p&gt;This process proves that the user owns the wallet without exposing private keys.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nonce Generation&lt;/strong&gt;&lt;br&gt;
A nonce is a one-time random string generated by the backend.&lt;br&gt;
Its main purpose is to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;prevent replay attacks&lt;/li&gt;
&lt;li&gt;ensure every login request is unique&lt;/li&gt;
&lt;li&gt;create a secure signing challenge
Example nonce generation:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;secrets&lt;/span&gt;

&lt;span class="n"&gt;nonce&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;token_hex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This generates a secure random hexadecimal string.&lt;br&gt;
Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;9f3d1a8bc44e1ff03c2e71ab93a12d4c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This nonce is usually stored temporarily in three ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Redis&lt;/li&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;in-memory cache
A common backend structure looks like this:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;nonces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wallet_address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;nonces&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nonce&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the frontend requests authentication, the backend responds with the nonce tied to that wallet address.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Signing Flow&lt;/strong&gt;&lt;br&gt;
Once the frontend receives the nonce, it asks the user to sign it using their wallet.&lt;/p&gt;

&lt;p&gt;The frontend typically uses libraries like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ethers.js&lt;/li&gt;
&lt;li&gt;web3.js&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example signing flow using ethers.js:&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;signer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSigner&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;signature&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;signMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this stage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the wallet does NOT send private keys&lt;/li&gt;
&lt;li&gt;the wallet only produces a cryptographic proof&lt;/li&gt;
&lt;li&gt;the backend can later verify ownership&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The frontend then sends:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;wallet address&lt;/li&gt;
&lt;li&gt;signature&lt;/li&gt;
&lt;li&gt;nonce&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;to the backend verification endpoint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verification of the Signature&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the core security layer of wallet authentication.&lt;/p&gt;

&lt;p&gt;The backend verifies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the signature is valid&lt;/li&gt;
&lt;li&gt;the signer actually owns the wallet&lt;/li&gt;
&lt;li&gt;the nonce matches the stored challenge&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using Web3.py and eth-account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;eth_account.messages&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;encode_defunct&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;web3&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Web3&lt;/span&gt;

&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;encode_defunct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;recovered_address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Web3&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;eth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recover_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;signature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;signature&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;recovered_address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;wallet_address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invalid signature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How this works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ethereum signatures can mathematically recover the signer’s wallet address.&lt;/li&gt;
&lt;li&gt;if the recovered address matches the submitted wallet address, authentication succeeds.&lt;/li&gt;
&lt;li&gt;if not, the request is rejected.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once verification succeeds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;delete the nonce&lt;/li&gt;
&lt;li&gt;prevent replay attacks&lt;/li&gt;
&lt;li&gt;continue authentication&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 python"&gt;&lt;code&gt;&lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;nonces&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;wallet_address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;JSON Web Token (JWT) Issuance&lt;/strong&gt;&lt;br&gt;
After successful verification, the backend issues a JWT token.&lt;/p&gt;

&lt;p&gt;JWTs allow authenticated access to protected endpoints without repeatedly signing messages.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;

&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sub&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;wallet_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SECRET_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;algorithm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HS256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The token contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;wallet address&lt;/li&gt;
&lt;li&gt;expiration time&lt;/li&gt;
&lt;li&gt;authentication claims&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The frontend stores this token and includes it in future API requests.&lt;br&gt;
Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Authorization: Bearer YOUR_JWT_TOKEN
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Best Practices&lt;/strong&gt;&lt;br&gt;
When building production-grade wallet authentication systems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use Expiring Nonces&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Never reuse a nonce.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use HTTPS&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Always encrypt requests.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add Rate Limiting&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Prevent spam attacks.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Store JWT Secret Securely&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Use environment variables.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add Wallet Chain Validation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ensure users sign from the correct network.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use Short JWT Expiration&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Reduce token abuse risk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
Wallet authentication is one of the foundational systems behind modern Web3 applications. While it may initially seem complex, the entire process boils down to a simple cryptographic challenge-response flow.&lt;/p&gt;

&lt;p&gt;Using FastAPI and Ethereum tools, you can build secure authentication systems that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;eliminate passwords&lt;/li&gt;
&lt;li&gt;improve user ownership&lt;/li&gt;
&lt;li&gt;simplify onboarding&lt;/li&gt;
&lt;li&gt;integrate seamlessly with dApps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you understand wallet authentication, building more advanced Web3 backend systems such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;wallet portfolio trackers&lt;/li&gt;
&lt;li&gt;NFT dashboards&lt;/li&gt;
&lt;li&gt;staking platforms&lt;/li&gt;
&lt;li&gt;DAO voting systems&lt;/li&gt;
&lt;li&gt;on-chain analytics APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;becomes significantly easier.&lt;br&gt;
Wallet authentication is becoming the standard identity layer for decentralized applications. Understanding this system gives backend developers a strong foundation for building secure Web3 infrastructure. The future of authentication is shifting toward ownership-based identity, and wallet authentication is one of the strongest examples of that transition. &lt;br&gt;
Happy Coding!&lt;/p&gt;

</description>
      <category>fastapi</category>
      <category>python</category>
      <category>web3</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>Built and deployed a Wallet Authentication API using FastAPI + PostgreSQL + Web3.py.</title>
      <dc:creator>Tunmise Ola</dc:creator>
      <pubDate>Wed, 13 May 2026 01:50:57 +0000</pubDate>
      <link>https://dev.to/tunmi_stom/built-and-deployed-a-wallet-authentication-api-using-fastapi-postgresql-web3py-h5h</link>
      <guid>https://dev.to/tunmi_stom/built-and-deployed-a-wallet-authentication-api-using-fastapi-postgresql-web3py-h5h</guid>
      <description>&lt;p&gt;Features:&lt;br&gt;
• Wallet signature verification&lt;br&gt;
• JWT authentication&lt;br&gt;
• Multi-chain support&lt;br&gt;
• Nonce-based security&lt;br&gt;
• Production-ready architecture&lt;/p&gt;

&lt;p&gt;Tech stack:&lt;br&gt;
FastAPI, SQLAlchemy, PostgreSQL, Web3.py&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/tunmi-stom/wallet_auth_api" rel="noopener noreferrer"&gt;https://github.com/tunmi-stom/wallet_auth_api&lt;/a&gt;&lt;br&gt;
Live API: &lt;a href="https://wallet-auth-api.onrender.com" rel="noopener noreferrer"&gt;https://wallet-auth-api.onrender.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>fastapi</category>
      <category>web3</category>
    </item>
  </channel>
</rss>
