TRADE

P2P marketplace in Telegram

Direct trades between users without an exchange middleman

✅ Ready👤 Solo backend🌍 P2P · Telegram botDemo on request

Context

It all started with a painting. Artist Holovnyi Heroi / Main Character ran a fundraising campaign — every cell on the canvas is a person who bought their fragment as a donation. Later his community wanted to trade these fragments among themselves. I built a system that does this conveniently and transparently — a P2P marketplace with a public deal registry. The task was to make it cheap, simple and effective.

painting · pieces-cells
Client's Instagram profile
open profile

My role: solo backend — from architecture to production. Telegram bot, background processor, integrations with Notion and Telegram User API, Docker deploy. No frontend — the interface is Telegram itself, and the public deal storefront is a Notion page.

You may know him from these videos

@main_main_character

Problem

The project owner wanted to give the community the ability to trade assets among themselves, without being a middleman in every deal. Off-the-shelf P2P exchanges didn't fit — custom logic for the community was needed.

Capabilities

Telegram bot with three-layer persistence and a background worker that guarantees no trade is lost even if the service crashes.

// system

Architecture

testing
Telegram users
SELL · BUY flows
command
Telegram Bot API
Bot Server
aiogram · async · FSM
Queue
Redis + FSM state
Background Processor
atomic 3-step commit
Archive Monitor
user API · side-car
SQL Ledger
private, transactional
Public Registry
Notion · CMS view
Whitelist
Excel + quotas
closed testing

Hard problems

01

Atomicity of a three-layer trade

Each trade has to be saved across three systems at once (queue → transactional DB → public registry). Any of them can fail. If the order is wrong — you get either a phantom trade with no public record, or a lost transaction.

Solution → A background processor acks the queue request only after successful INSERT and append in both stores. If the public publish failed — re-check whether the entry already exists (race-safe), then retry. Guarantees "at-least-once delivery" without duplicates.

02

Anonymity + audit at the same time

The public registry must not expose real Telegram-IDs of participants. But for dispute review you need a full archive of conversations tied to the real ID.

Solution → Two-layer identity system: fake-ID for public records, real-ID kept privately in the transactional DB only. The conversation archive is stored under the real ID in a file store accessible only to the admin.

03

Concurrency without race conditions

A classic threaded bot suffers from FSM race conditions — user state can be overwritten by two callbacks at once.

Solution → Fully async architecture, FSM state stored in a shared store (not in process memory), background processor handles the queue sequentially. You can run multiple bot instances — state is shared.

04

Two Telegram clients in one project

Bot API doesn't give access to full message history (only new messages). For a complete archive you need the user-account API with separate auth.

Solution → A separate side-car process on the user-account API sits in the background and writes history to a file archive. Doesn't block the main bot, runs independently. A third process (monitoring) under tmux with separate logs.

Tech stack

Backend / runtime

Python 3.13aiogram 3asyncioaiohttphttpx

Persistence

Redis (queue + FSM)SQLite (transactional ledger)Notion API (public ledger)JSON archive

Integrations

Telegram Bot APITelegram User API (Telethon)Excel / openpyxl (whitelist)

Infrastructure

Docker (linux/amd64)tmuxbash orchestration

What this gives vs. a centralized exchange

A custom P2P bot for a community — where off-the-shelf exchanges don't let you set your own rules.

Custom rules
Exchange
impossible
Trade
full control (whitelist, quotas, scripts)
Per-trade fee
Exchange
0.1–1%
Trade
0
Trade transparency
Exchange
private exchange logs
Trade
public Notion registry
Platform dependence
Exchange
full (can be shut down, banned)
Trade
none — own code, own server
Participant anonymity
Exchange
KYC mandatory
Trade
fake-ID, no KYC
Dispute archive
Exchange
unavailable to users
Trade
full JSON archive of conversations

What's next?

This is one of my cases. The rest is on the home page.