Today I built a small project using Supabase as a backend. The project is called VanityLib, and it’s a public directory of rare Bitcoin addresses — each one pre-generated with a readable prefix and paired with a private key. The user can browse them in a table and pick one. Once selected, the address should be removed from the database.

I started by signing up for Supabase and creating a new project. Then I created a PostgreSQL table named addresses with the following schema:

CREATE TABLE addresses (
  id bigint generated by default as identity primary key,
  public_key text not null,
  private_key text not null,
  pattern text
);

I imported the data using a .csv file containing rows like:

BTC,1Actor,1ActorRiBSCVn5yAFCB9mWf5eUb9TtirJh,5JJ2WpyHevdyqKzYapDjcDmZx7XDcgaSNFqUn8g9UbRdU2fxXVN

Only public_key, private_key, and pattern were relevant. I uploaded the data via the Supabase UI.

On the frontend, I used plain HTML, CSS, and JavaScript. Supabase provides an ESM-compatible client via CDN:

import { createClient } from 'https://cdn.jsdelivr.net/npm/@supabase/supabase-js/+esm';
const supabase = createClient(SUPABASE_URL, ANON_KEY);

From there, I queried the addresses table using .select() and rendered the results in a table. Rarity is derived by inspecting the length of the pattern column. I mapped it to a set of emoji symbols to represent the quality of the vanity prefix:

const rarityLevels = ['🪵', '🥄', '🔩', '🪙', '💎', '🧊', '🌌'];
const rarity = rarityLevels[Math.min(Math.max(patternLength - 3, 0), rarityLevels.length - 1)];

Private keys are hidden by default. Clicking the “Click to show” cell triggers a secondary Supabase query which fetches the corresponding private key via:

const { data } = await supabase
  .from('addresses')
  .select('private_key')
  .eq('public_key', publicKey)
  .single();

I also added pagination using .range(page * pageSize, (page + 1) * pageSize - 1) and a couple of navigation buttons that let users load the next or previous page.

Next steps are to:

  • Remove a row from the table when the private key is revealed (via .delete()),
  • Possibly replace the static site with a small Svelte or Astro frontend for better structure,
  • Add serverless Supabase functions for rate limiting or ownership logging.

This was the first time I used Supabase seriously, and the experience was solid. The setup was fast, and the client API is straightforward. No backend code needed, just client-side logic and some access control rules in the Supabase UI.

I decided to not publish the project since it can be misused.