Torna indietro

La tua prima Blockchain in TypeScript

Foto di Tobia Bartolomei

14 apr 2025Tobia Bartolomei

Ciao dev! 👋

In questo articolo ti porto passo dopo passo nella creazione di una blockchain funzionante in TypeScript.

Vedremo come costruire blocchi, effettuare transazioni, simulare un wallet e validare l’intera chain. È un esercizio fantastico per capire come funziona davvero una blockchain.

Requisiti di base

Per iniziare, assicurati di avere:

  • Node.js
  • TypeScript
  • Un minimo di conoscenza su classi, array e oggetti in TypeScript

Struttura del progetto

Creiamo una struttura semplice:

📄Files Structure
1/blockchain
2  ├── lib/
3  │   ├── blockchain/
4  │   │   └── main.ts
5  │   ├── wallet.ts
6  │   ├── data.ts
7  └── types.ts

Nel file types.ts definiamo la struttura di una transazione:

📄types.ts
1export interface Transaction {
2  senderAddress: string;
3  receiverAddress: string;
4  amount: number;
5  note?: string;
6  timestamp: string;
7  fee: number;
8}

Nel file data.ts definiamo i nostri utenti fittizi:

📄data.ts
1export const user1 = {
2  address: "user1-address",
3  balance: 100
4};
5
6export const user2 = {
7  address: "user2-address",
8  balance: 100
9};

La classe Wallet

Nel file wallet.ts andiamo a creare una classe per i portafogli dei nostri utenti, in modo da gestire bilanci e transazioni più comodamente:

📄wallet.ts
1import { Transaction } from './types';
2
3interface Balance {
4  address: string;
5  balance: number;
6}
7
8export class Wallet {
9  balances: Balance[];
10
11  constructor(initialBalances: Balance[]) {
12    this.balances = initialBalances;
13  }
14
15  getBalance(address: string): number {
16    const user = this.balances.find(b => b.address === address);
17    return user ? user.balance : 0;
18  }
19
20  hasEnoughBalance(address: string, amount: number): boolean {
21    return this.getBalance(address) >= amount;
22  }
23
24  processTransaction(tx: Transaction): boolean {
25    const sender = this.balances.find(b => b.address === tx.senderAddress);
26    const receiver = this.balances.find(b => b.address === tx.receiverAddress);
27
28    if (!sender || sender.balance < tx.amount + tx.fee) return false;
29
30    sender.balance -= tx.amount + tx.fee;
31
32    if (receiver) {
33      receiver.balance += tx.amount;
34    } else {
35      this.balances.push({ address: tx.receiverAddress, balance: tx.amount });
36    }
37
38    return true;
39  }
40
41  checkDoubleSpending(pending: Transaction[]): boolean {
42    const tempBalances = [...this.balances.map(b => ({ ...b }))];
43    for (const tx of pending) {
44      const sender = tempBalances.find(b => b.address === tx.senderAddress);
45      if (!sender || sender.balance < tx.amount + tx.fee) return true;
46      sender.balance -= tx.amount + tx.fee;
47    }
48    return false;
49  }
50}

main.ts: Blockchain e blocchi

Ora la parte centrale: creiamo blocchi e la blockchain. Qui mettiamo tutto insieme: transazioni, mining e validazione.

Ogni blocco rappresenta un gruppo di transazioni, e contiene:

  • Timestamp:
  • Lista di transazioni
  • Hash del blocco precedente
  • Hash (calcolato con SHA256)
  • Nonce (per il mining)
  • Fee totale delle transazioni
📄main.ts
1import { SHA256 } from 'crypto-js';
2import { Transaction } from '../types';
3import { Wallet } from '../wallet';
4import { user1, user2 } from '../data';
5
6export class Block {
7  index: number;
8  timestamp: string;
9  transactions: Transaction[];
10  previousHash: string;
11  hash: string;
12  nonce: number;
13  fee: number;
14
15  constructor(timestamp: string, transactions: Transaction[]) {
16    if (transactions.length > 10) throw new Error("Max 10 transazioni per blocco.");
17    this.index = 0;
18    this.timestamp = timestamp;
19    this.transactions = transactions;
20    this.previousHash = "0";
21    this.hash = this.calculateHash();
22    this.nonce = 0;
23    this.fee = 0;
24  }
25
26  calculateHash() {
27    return SHA256(
28      this.index +
29      this.previousHash +
30      this.timestamp +
31      JSON.stringify(this.transactions) +
32      this.nonce
33    ).toString();
34  }
35
36  mineBlock(difficulty: number) {
37    const target = Array(difficulty + 1).join("0");
38    while (this.hash.substring(0, difficulty) !== target) {
39      this.nonce++;
40      this.hash = this.calculateHash();
41    }
42  }
43}

La classe Blockchain

Questa classe tiene insieme tutta la chain, le transazioni in sospeso e un portafoglio fittizio:

📄main.ts
1export class Blockchain {
2  chain: Block[];
3  pendingTransactions: Transaction[] = [];
4  wallet: Wallet;
5
6  constructor(existingChain?: Block[], existingWallet?: Wallet) {
7    this.chain = existingChain || [this.createGenesis()];
8    this.wallet = existingWallet || new Wallet([
9      { address: user1.address, balance: user1.balance },
10      { address: user2.address, balance: user2.balance }
11    ]);
12  }
13
14  createGenesis() {
15    return new Block(new Date('2025-01-01').toISOString(), [{
16      senderAddress: 'chain',
17      receiverAddress: 'chain',
18      amount: 0,
19      note: 'Genesis Block',
20      timestamp: new Date('2025-01-01').toISOString(),
21      fee: 0
22    }]);
23  }
24
25  latestBlock() {
26    return this.chain[this.chain.length - 1];
27  }
28
29  addBlock(newBlock: Block) {
30    newBlock.index = this.latestBlock().index + 1;
31    newBlock.previousHash = this.latestBlock().hash;
32    newBlock.mineBlock(4);
33    this.chain.push(newBlock);
34  }
35
36  addTransaction(tx: Transaction) {
37    if (!this.wallet.hasEnoughBalance(tx.senderAddress, tx.amount + tx.fee)) {
38      throw new Error("Saldo insufficiente.");
39    }
40
41    if (this.wallet.checkDoubleSpending([...this.pendingTransactions, tx])) {
42      throw new Error("Double spending rilevato.");
43    }
44
45    if (!this.wallet.processTransaction(tx)) {
46      throw new Error("Errore durante il processamento.");
47    }
48
49    this.pendingTransactions.push(tx);
50
51    if (this.pendingTransactions.length === 10) {
52      const newBlock = new Block(new Date().toISOString(), [...this.pendingTransactions]);
53      this.addBlock(newBlock);
54      this.pendingTransactions = [];
55    }
56  }
57
58  minePendingTransactions() {
59    if (this.pendingTransactions.length > 0) {
60      const newBlock = new Block(new Date().toISOString(), [...this.pendingTransactions]);
61      this.addBlock(newBlock);
62      this.pendingTransactions = [];
63      return true;
64    }
65    return false;
66  }
67
68  getPendingTransactions(): Transaction[] {
69    return [...this.pendingTransactions];
70  }
71
72  checkValid(): boolean {
73    for (let i = 1; i < this.chain.length; i++) {
74      const current = this.chain[i];
75      const previous = this.chain[i - 1];
76
77      if (current.hash !== current.calculateHash()) return false;
78      if (current.previousHash !== previous.hash) return false;
79      if (current.transactions.length > 10 || current.transactions.length === 0) return false;
80      for (const tx of current.transactions) {
81        if (!tx.senderAddress || !tx.receiverAddress) return false;
82      }
83    }
84    return true;
85  }
86
87  calculateFee(amount: number): number {
88    const baseFee = 0.0010;
89    const amountFee = amount * 0.010;
90    const networkLoadFactor = Math.min(1 + (this.pendingTransactions.length / 10), 2);
91    return (baseFee + amountFee) * networkLoadFactor;
92  }
93
94  getBalance(address: string): number {
95    return this.wallet.getBalance(address);
96  }
97}

Risultato

Hai ora una blockchain funzionante in locale con:

  • Fee dinamiche
  • Wallet con bilanci
  • Mining proof-of-work
  • Transazioni sicure (no double spending)

Conclusione

Questo è solo l’inizio. Puoi estendere il progetto con firme digitali, una rete distribuita, meccanismi di consenso, e magari un’interfaccia frontend per esplorare i blocchi!


Ti interessa vedere anche una versione online o una demo? Visita la mia demo pubblica a questo link!