DEV Community

Cover image for Building an Independent Layer-1 Blockchain From Scratch — SAYANJALI BLOCKCHAIN v0.1.0-mvp published
Syed Ali Hasan Moosavi
Syed Ali Hasan Moosavi

Posted on

Building an Independent Layer-1 Blockchain From Scratch — SAYANJALI BLOCKCHAIN v0.1.0-mvp published

Most projects that describe themselves as "blockchain" are a smart
contract deployed on Ethereum, Solana, or a similar host network.
That's
often the right call — you inherit a mature validator set, existing
tooling, and liquidity on day one.
It also means you inherit that network's fee market, its execution
constraints, and its governance. Your token's economics are ultimately
subject to decisions you don't make.
For SAYANJALI NEXUS, I wanted the settlement layer for the SYJ
Token to be a protocol we actually control end to end — its own block
format, its own consensus rules, its own upgrade path. That means building
a chain, not a contract.
This post covers what shipped in v0.1.0-mvp, the architectural
decisions behind it, and what's intentionally not in this release yet.
Repository: github.com/SHalimoosavi/SAYANJALI-BLOCKCHAIN
Scope of this release
An MVP is a bad place to cut corners on fundamentals and a good place to
defer everything else. So this release focuses entirely on getting the
core primitives correct and tested, and defers anything that depends on
a live network of peers:
Included:
Block model, SHA-256 hashing, Merkle-root transaction integrity
Full chain validation from genesis to tip
Proof-of-Work consensus with configurable difficulty
ECDSA (SECP256k1) wallets
Signed, independently verifiable transactions
A mempool with duplicate and signature validation
SQLAlchemy-backed persistence
A FastAPI REST interface and a Typer CLI
37 automated tests
Deliberately deferred:
Peer-to-peer networking
Proof of Stake
Smart contract execution
A block explorer
Governance tooling

Every deferred item is architected for — not bolted on later. That distinction shows up in a few specific design choices worth walking through.
Design decision: header-only block hashing
A block's hash only covers its header fields — index, previous hash, timestamp, nonce, difficulty, and Merkle root — not the full transaction
List:
**def header_payload(self) -> dict[str, Any]:
return {
"index": self.index,
"previous_hash": self.previous_hash,
"timestamp": self.timestamp,
"nonce": self.nonce,
"difficulty": self.difficulty,
"merkle_root": self.merkle_root,
}

def compute_hash(self) -> str:
return sha256(deterministic_json(self.header_payload()))**

Mining repeatedly re-hashes the header on every nonce attempt, so keeping that payload small and fixed-size means mining cost stays independent of block size. Transaction integrity is still fully guaranteed, because the
Merkle root is itself part of the header — any change to any transaction changes the Merkle root, which changes the hash.
Design decision: difficulty lives inside the block
Difficulty in most tutorial-grade blockchain implementations is a global config value, checked externally against whatever the block claims. That creates an awkward problem the moment difficulty legitimately changes over time: how do you validate historical blocks that were mined under a different difficulty than the one your config currently holds?
***SAYANJALI BLOCKCHAIN records difficulty as a field on the block itself,
and that field is part of the hashed header — so it can't be tampered with
independently of the block's
* hash:**
Python:
**def mine(self, block: Block, difficulty: int) -> Block:
target_prefix = "0" * difficulty
block.nonce = 0
block.difficulty = difficulty
block.recompute()

while not block.hash.startswith(target_prefix):
    block.nonce += 1
    block.recompute()

return block
Enter fullscreen mode Exit fullscreen mode

**
Validation then checks a block against its own recorded difficulty (authenticated by the hash) rather than a single static expectation, which lets difficulty retarget over the network's history without
invalidating blocks mined before the retarget.
Design decision: consensus behind an interface Consensus-Engine is an abstract base class with one implementation today:
Python: class Consensus **Engine(ABC):
@abstractmethod
def mine(self, block: Block, difficulty: int) -> Block: ...

@abstractmethod
def validate(self, block: Block, difficulty: int) -> bool: ...

@abstractmethod
def next_difficulty(
    self, previous_blocks: list[Block],
    config_difficulty: int, target_block_time: int,
) -> int: ...
Enter fullscreen mode Exit fullscreen mode

**
Proof Of Work Consensus implements it now. Proof of Stake or Delegated
Proof of Stake become new classes implementing the same interface —
nothing in the orchestrator, API, or CLI needs to change to support them.
Design decision: storage abstraction, not a database driver Persistence is built on SQLAlchemy Core rather than raw sqlite3 calls.

The MVP ships with SQLite because it needs zero infrastructure to run, but the entire storage layer talks to SQLAlchemy's engine interface — moving to PostgreSQL later is a one-line change to a connection string, not a
*rewrite of storage.py:
Python:
@property
def database_url(self) -> str:
self.database_dir.mkdir(parents=True, exist_ok=True)
return f"sqlite:///{self.database_dir / self.database_file}"
*

What operating a node looks like
Two interfaces, one underlying chain implementation — the REST API and the CLI both call into the same Blockchain facade, so there's exactly one source of truth for how the chain behaves.
**CLI:
Bash:
python -m cli.main create-wallet
python -m cli.main mine


python -m cli.main show-chain
python -m cli.main validate

REST API :
Bash:
uvicorn api.main:app --host 0.0.0.0 --port 8000

curl -X POST http://127.0.0.1:8000/wallet/create

curl -X POST http://127.0.0.1:8000/mine \
-H "Content-Type: application/json" \
-d '{"miner_address": "

"}'

curl http://127.0.0.1:8000/status
**
Swagger docs are generated automatically at /docs.
Testing
37 tests cover wallets, transactions, blocks, mining, chain validation,
and the full REST API via FastAPI's TestClient, each running against an
isolated temporary SQLite database:
Bash : pytest -v
37 passed in 1.2s

Two bugs the test suite actually caught during development, worth
mentioning because they're the kind of thing that's easy to miss in a from-scratch implementation:
A validation/mining mismatch — mining used a dynamically retargeted difficulty, but chain validation checked against one static config value, so a chain that had legitimately retargeted would fail its own validation. Fixed by recording difficulty per block, as described above.
A coinbase tamper gap — coinbase (mining reward) transactions carry no signature, so nothing was verifying their amount field hadn't been altered after the transaction hash was computed. Fixed by having Transaction.verify() check the transaction's hash against a fresh recomputation, catching any post-hash tampering regardless of whether a signature exists.
Both are the kind of thing you only find by actually mining blocks, tampering with them, and asserting the chain rejects the tampering — not by reading the code.
What's next
Phase

Scope
6 Peer-to-peer networking, multi-node synchronization
7 Full difficulty retargeting, Proof-of-Stake groundwork
8 Block explorer
9 Smart contract execution
10 Governance, SDK
11 Mainnet, SYJ Token launch
*Try it
Bash :
git clone https://github.com/SHalimoosavi/SAYANJALI-BLOCKCHAIN.git
cd SAYANJALI-BLOCKCHAIN
python -m venv venv && source venv/bin/activate
pip install -r requirements.txt
python -m cli.main create-wallet
*

This is an MVP — unaudited, not production-ready, and built to be read and reviewed rather than taken on faith. If you work on consensus systems, distributed storage, or Python infrastructure, I'd genuinely value a look at the design decisions above. Issues, forks, and pull requests are all
welcome.

Repository: https://github.com/SHalimoosavi/SAYANJALI-BLOCKCHAIN
Release notes: https://github.com/SHalimoosavi/SAYANJALI-BLOCKCHAIN/releases/tag/v0.1.0-mvp


Top comments (0)