🧠 Was ist ein Qubic Smart Contract? What is a Qubic Smart Contract?
Ein Smart Contract (SC) auf Qubic ist eine einzelne C++-Datei, die mit der Qubic Public Interface (QPI) geschrieben wird. Der Code läuft nicht auf einem Server — er läuft gleichzeitig auf allen 676 Qubic-Computern (den sogenannten "Computors") im Netzwerk.
- Kein Server nötig — der Code ist immer online, solange das Qubic-Netzwerk läuft
- Unveränderlich — einmal deployed kann der Code nicht heimlich geändert werden
- Transparent — jeder kann den Code lesen und prüfen
- Kein Vertrauen nötig — der SC erzwingt die Regeln, kein Mensch entscheidet
A Smart Contract (SC) on Qubic is a single C++ file written using the Qubic Public Interface (QPI). The code does not run on a server — it runs simultaneously on all 676 Qubic computers ("Computors") in the network.
- No server needed — the code is always online as long as the Qubic network runs
- Immutable — once deployed, the code cannot be secretly changed
- Transparent — anyone can read and verify the code
- Trustless — the SC enforces the rules; no human decides
⚡ Warum funktioniert QubicShield nur auf Qubic? Why does QubicShield only work on Qubic?
Das Herzstück von QubicShield ist die Rückzahlung des Deposits an legitime Nutzer. Das kostet auf anderen Blockchains Geld:
The core of QubicShield is the refund of the deposit to legitimate users. On other blockchains this costs money:
| Blockchain | ProblemProblem |
|---|---|
| Ethereum |
Jede transfer()-Transaktion kostet Gas. Bei einem Deposit von 0,001 EUR kostet die Rückzahlung oft mehr als der Deposit → Modell kaputt
Every transfer() transaction costs gas. With a deposit of $0.001, the refund gas fee often exceeds the deposit itself → model breaks
|
| Bitcoin | Keine Smart Contracts mit dieser Logik möglich No smart contracts with this kind of logic |
| Qubic |
Zero Transaction Fees — Rückzahlungen kosten nichts. Das Modell funktioniert.
Zero Transaction Fees — refunds cost nothing. The model works.
|
Ursprung der IdeeOrigin of the Idea
Die Grundidee — Rechenarbeit oder Zahlung als Schutz vor Missbrauch — ist nicht neu. Aber niemand hat sie bisher als vollständiges Webprodukt umgesetzt:
The core idea — work or payment as a barrier against abuse — is not new. But nobody has realized it as a complete web product:
| JahrYear | WerWho | WasWhat |
|---|---|---|
| 1993 | Dwork & Naor | „Pricing via Processing" — erste Idee, Rechenarbeit als Anti-Spam-Kosten"Pricing via Processing" — first idea of computation as anti-spam cost |
| 1997 | Adam Back | Hashcash — Proof-of-Work für E-Mail-Spam (Vorläufer von Bitcoin-Mining). Rechenarbeit statt Geld, nicht rückerstattbar.Hashcash — proof-of-work for email spam (predecessor of Bitcoin mining). Computation instead of money, not refundable. |
| ~2015+ | Lightning Network | HTLCs — rückerstattbare Zahlungen technisch möglich, aber kein fertiges Web-Proxy-ProduktHTLCs — refundable payments technically possible, but no finished web proxy product |
| 2026 | QubicShield |
Erste vollständige Umsetzung — rückzahlbares Deposit + Web-Zugang + wirtschaftlicher Spam-Schutz + Konfiszierung bei Angriff. Möglich durch Qubics Zero-Fee-Architektur.
First complete implementation — refundable deposit + web access + economic spam protection + forfeit on attack. Made possible by Qubic's zero-fee architecture.
|
🔄 Der Unterschied: Mock vs. echter Smart Contract The Difference: Mock vs. Real Smart Contract
Im Demo-Server gibt es eine Datei src/depositManager.ts. Die sieht professionell aus, aber sie ist ein Mock — alle Daten leben nur im RAM:
The demo server has a file src/depositManager.ts. It looks professional but it is a mock — all data lives only in RAM:
- Deposits:
JavaScript Map()— weg bei NeustartDeposits:JavaScript Map()— lost on restart - Transfers: simuliert, kein echtes GeldTransfers: simulated, no real money
- Vertrauen: du musst dem Server vertrauenTrust: you must trust the server operator
- Deposits: Blockchain — permanent, unveränderlichDeposits: Blockchain — permanent, immutable
- Transfers:
qpi.transfer()— echtes GeldTransfers:qpi.transfer()— real money - Vertrauen: niemand nötig — der Code ist die RegelTrust: nobody needed — the code is the rule
🗺️ Die Übersetzung: Mock → Smart Contract The Translation: Mock → Smart Contract
Jede Funktion im Mock hat ein Gegenstück im SC:
Every function in the mock has a counterpart in the SC:
| depositManager.ts | QubicShield.h | Typ |
|---|---|---|
createDeposit() | PUBLIC_PROCEDURE(Deposit) | Procedure |
refundDeposit() | PUBLIC_PROCEDURE(Refund) | Procedure |
forfeitDeposit() | PUBLIC_PROCEDURE(Forfeit) | Procedure |
validateToken() | PUBLIC_FUNCTION(ValidateSession) | Function |
getStats() | PUBLIC_FUNCTION(GetStats) | Function |
| Auto-Expire (setTimeout)Auto-Expire (setTimeout) | BEGIN_TICK Hook | Tick-Hook |
⚖️Procedure vs. Function
In Qubic gibt es zwei Arten von Entry Points — der wichtigste Unterschied:
In Qubic there are two types of entry points — the most important distinction:
- Darf State verändernMay modify state
- Darf QUBIC transferierenMay transfer QUBIC
- Kostet ComputeCosts compute
- Beispiele: Deposit, Refund, ForfeitExamples: Deposit, Refund, Forfeit
- Darf State nur lesenMay only read state
- Kein Transfer möglichNo transfers allowed
- Günstig, sicher öffentlichCheap, safely public
- Beispiele: ValidateSession, GetStatsExamples: ValidateSession, GetStats
🔒 QPI-Einschränkungen — und warum sie existieren QPI Constraints — and why they exist
QPI ist kein normales C++. Die Einschränkungen sind Absicht — sie machen den SC sicher und deterministisch:
QPI is not regular C++. The constraints are intentional — they make the SC safe and deterministic:
| EinschränkungConstraint | WarumWhy | LösungSolution |
|---|---|---|
#include verbotenforbidden |
Keine externen Libraries → keine versteckte LogikNo external libraries → no hidden logic | QPI Built-in TypenQPI built-in types |
/ undand % |
Undefined Behaviour bei Division durch 0Undefined behaviour on division by zero | div(a,b) / mod(a,b) |
string |
Variable Länge → nicht deterministischVariable length → not deterministic | id Typ (60-Byte Hash)type (60-byte hash) |
new / Pointerpointers |
Dynamischer Speicher nicht vorhersehbarDynamic memory is unpredictable | Array<T, N> |
bool / int / char |
Plattformabhängige GrößenPlatform-dependent sizes | bit, sint64, uint32, uint8 |
| UTC-ZeitUTC time | Server-Uhren nicht konsistent über 676 NodesClocks not consistent across 676 nodes | qpi.tick() |
🗄️ Die Datenstruktur: DepositEntry The Data Structure: DepositEntry
Jedes Deposit wird als DepositEntry Struct gespeichert. Da es keinen string-Typ gibt, nutzen wir QPI-eigene Typen:
Every deposit is stored as a DepositEntry struct. Since there is no string type, we use QPI-native types:
struct DepositEntry
{
id owner; // Wallet-Adresse (60-byte Hash)wallet address (60-byte hash)
id token; // Session-Token (K12-Hash)session token (K12 hash)
sint64 amount; // Betrag in QUBIC-Einheitenamount in QUBIC units
uint32 createdTick; // qpi.tick() beim Erstellenat creation
uint32 expiresAtTick; // createdTick + 3600 ticks (≈ 30 Minmin)
uint8 status; // 0=leerempty 1=held 2=refunded 3=forfeited
uint32 requestCount; // Anzahl Requestsnumber of requests
};
Warum 512 Slots? Das ist die maximale Anzahl gleichzeitiger Sessions. Mehr kostet mehr Speicher auf jedem der 676 Nodes.
Why 512 slots? That is the maximum number of concurrent sessions. More costs more storage on each of the 676 nodes.
Array<DepositEntry, 512> deposits;
#️⃣
Der id Typ und K12-Hashing
The id Type and K12 Hashing
id ist ein QPI Built-in Typ — ein 60-Byte-Wert der typischerweise eine Wallet-Adresse oder einen Hash darstellt.
Wie wird der Session-Token erzeugt?
id is a QPI built-in type — a 60-byte value that typically represents a wallet address or hash.
How is the session token generated?
id caller = qpi.invocator(); // Wallet-Adresse des Aufruferswallet address of the caller
id sessionToken = qpi.K12(caller); // K12-Hash → einzigartiges Tokenunique token
qpi.K12() ist Qubics eigene Hash-Funktion. Der Token ist:
- Einzigartig pro Wallet
- Nicht vorhersagbar von außen
- Verifizierbar — der Proxy prüft ihn mit
ValidateSession()
qpi.K12() is Qubic's own hash function. The token is:
- Unique per wallet
- Unpredictable from outside
- Verifiable — the proxy checks it with
ValidateSession()
⏱️ Tick-basierte Zeit Tick-Based Time
Qubic hat keine Echtzeituhr. Stattdessen gibt es Ticks — einen globalen Zähler der vom Netzwerk hochgezählt wird.
- Aktuell mehr als 2 Ticks pro Sekunde (Intervall < 1 Sek)
- 3.600 Ticks ≈ weniger als 30 Minuten (je nach aktueller Tick-Rate)
qpi.tick()gibt den aktuellen Tick zurück
Qubic has no real-time clock. Instead there are ticks — a global counter incremented by the network.
- Currently more than 2 ticks per second (interval < 1 sec)
- 3,600 ticks ≈ less than 30 minutes (depends on current tick rate)
qpi.tick()returns the current tick
entry.expiresAtTick = qpi.tick() + SESSION_DURATION_TICKS; // +3600
BEGIN_TICK Hook läuft automatisch ein Scan — abgelaufene Sessions werden forfeitiert ohne dass jemand aktiv etwas tun muss.
The BEGIN_TICK hook runs an automatic scan — expired sessions are forfeited without anyone having to do anything.
🔁Hooks: BEGIN_EPOCH & BEGIN_TICK
Qubic ruft diese Blöcke automatisch auf — kein externer Trigger nötig.
Qubic calls these blocks automatically — no external trigger needed.
BEGIN_EPOCH / END_EPOCH
Läuft ca. einmal pro Woche. Bei uns: Initialisierung des Operators beim ersten Deploy.
Runs approx. once per week. Here: operator initialization on first deploy.
BEGIN_EPOCH
{
id zeroId;
if (operator_ == zeroId) // Erste Epoche = noch nicht initialisiertfirst epoch = not yet initialized
{
operator_ = qpi.originator(); // Deployer wird Operatordeployer becomes operator
}
}
END_EPOCH
BEGIN_TICK / END_TICK
Läuft aktuell mehr als 2× pro Sekunde (Intervall < 1 Sek). Bei uns: automatisches Expire abgelaufener Sessions.
Currently runs more than 2× per second (interval < 1 sec). Here: automatic expiry of stale sessions.
🛡️ Sicherheit: Wie verhindert der SC Betrug? Security: How does the SC prevent fraud?
Problem: Fremde Deposits zurückfordern Problem: Claiming someone else's deposit
Beim Refund() müssen BEIDE Werte übereinstimmen:
For Refund() BOTH values must match:
- Der korrekte
sessionIndex(Position im Array)The correctsessionIndex(position in array) - Das korrekte
token(K12-Hash) — kennt nur der originale DepositorThe correcttoken(K12 hash) — only the original depositor knows it
if (entry.token != input.token)
{
output.success = 0;
output.errorCode = 2; // falsches Token → abgelehntwrong token → rejected
return;
}
Forfeit: Nur der Operator darf forfeitten Forfeit: Only the operator may forfeit
if (qpi.invocator() != operator_)
{
output.success = 0;
output.errorCode = 2; // nicht der Operator → abgelehntnot the operator → rejected
return;
}
📋 Registrierung der Entry Points Registering Entry Points
Jede Procedure und Function muss mit einem fixen Index registriert werden. Der Index ist die "Telefonnummer" über die externe Aufrufer den Entry Point adressieren.
Every procedure and function must be registered with a fixed index. The index is the "phone number" external callers use to address the entry point.
REGISTER_USER_FUNCTIONS_AND_PROCEDURES
{
REGISTER_USER_PROCEDURE(Deposit, 1);
REGISTER_USER_PROCEDURE(Refund, 2);
REGISTER_USER_PROCEDURE(Forfeit, 3);
REGISTER_USER_PROCEDURE(WithdrawForfeited, 6); // NEW — index 6
REGISTER_USER_FUNCTION(ValidateSession, 4);
REGISTER_USER_FUNCTION(GetStats, 5);
}
🌐 Gesamtbild: Wie alles zusammenspielt Big Picture: How everything works together
⚔️ QubicShield vs. Cloudflare QubicShield vs. Cloudflare
| Cloudflare | QubicShield | |
|---|---|---|
| AnsatzApproach | Technik filtert AngriffeTechnology filters attacks | Wirtschaft verhindert AngriffeEconomics prevents attacks |
| ArchitekturArchitecture | ZentralisiertCentralized | DezentralDecentralized |
| Traffic-SichtbarkeitTraffic visibility | Cloudflare sieht allesCloudflare sees everything | Kein Traffic-Routing nötigNo traffic routing needed |
| Single Point of FailureSingle point of failure | JaYes | NeinNo |
| Kosten ohne AngriffCost without attack | Monatliches AboMonthly subscription | Nahezu nichtsNear zero |
| DatenschutzPrivacy | Traffic läuft über fremde ServerTraffic passes through their servers | DSGVO-konform by DesignGDPR-friendly by design |
| LangfristzielLong-term goal | Angriffe managenManage attacks | Angriffe irrational machenMake attacks irrational |
🔗 QubicShield vs. QubicShield Node QubicShield vs. QubicShield Node
Die beiden Projekte sind keine Konkurrenten — das eine ist das Protokoll, das andere ist die Infrastruktur. QubicShield läuft heute als Middleware direkt im Web-Server und funktioniert vollständig ohne einen Node.
The two projects are not competitors — one is the protocol, the other is the infrastructure. QubicShield runs today as middleware directly inside the web server and works fully without any node.
| QubicShield (dieser PoC)QubicShield (this PoC) | QubicShield NodeQubicShield Node | |
|---|---|---|
| EbeneLayer | L7 (AnwendungApplication) | L3–L7 (Netzwerk + AnwendungNetwork + Application) |
| PositionPosition | Im Web-ServerInside the web server | Vor dem Web-ServerIn front of the web server |
| Unauthentifizierter TrafficUnauthenticated traffic | Erreicht den ServerReaches the server | Wird davor blockiertBlocked before the server |
| Standalone nutzbarUsable standalone | JaYes | Nein — braucht das Protokoll als KernNo — needs the protocol as its core |
| Status | Funktionierender PoCWorking PoC | Konzept / VisionConcept / Vision |
💸 WithdrawForfeited — Wie der Operator QUBIC abheben kann WithdrawForfeited — How the Operator Withdraws QUBIC
Forfeitierte QUBIC blieben bisher für immer im Contract. Die neue PUBLIC_PROCEDURE(WithdrawForfeited) erlaubt es dem Operator, dieses QUBIC auf seine Wallet zu transferieren.
Forfeited QUBIC previously stayed locked in the contract forever. The new PUBLIC_PROCEDURE(WithdrawForfeited) lets the operator transfer this QUBIC to their wallet.
Was ist qpi.contractBalance()?
Jeder SC hat eine eigene Wallet-Adresse auf der Blockchain. qpi.contractBalance() gibt den echten, aktuellen On-Chain-Saldo zurück — also das QUBIC das wirklich dort liegt.
What is qpi.contractBalance()?
Every SC has its own wallet address on the blockchain. qpi.contractBalance() returns the real, current on-chain balance — the QUBIC that is actually there.
totalForfeited — was laut unserem Code dort sein solltewhat our code says should be there
contractBalance() — was wirklich dort istwhat is actually there
contractBalance()-Check, dass wir mehr transferieren als wirklich vorhanden ist.
Defensive programming: We check both values. If there were a bug in accounting logic, the contractBalance() check prevents transferring more than actually exists.
PUBLIC_PROCEDURE(WithdrawForfeited)
{
if (qpi.invocator() != operator_) { /* reject */ return; }
if (input.amount > totalForfeited) { /* reject */ return; }
if (input.amount > qpi.contractBalance()) { /* reject */ return; }
qpi.transfer(operator_, input.amount);
totalForfeited = totalForfeited - input.amount;
}
🏛️ IPO, 676 Anteile & Forfeit-Verteilung IPO, 676 Shares & Forfeit Distribution
Wenn QubicShield als echter Qubic Smart Contract veröffentlicht wird, durchläuft er eine Dutch Auction (IPO). Alle QUBIC aus dem IPO werden sofort verbrannt — das ist Protokoll. Der SC bekommt dabei genau 676 Anteile, einer für jeden der 676 Computors im Netzwerk.
When QubicShield is published as a real Qubic smart contract, it goes through a Dutch Auction (IPO). All QUBIC from the IPO is immediately burned — that is the protocol. The SC receives exactly 676 shares, one for each of the 676 Computors in the network.
QubicShield — Forfeit-VerteilungQubicShield — Forfeit Distribution
Wenn ein Angreifer forfeitiert wird, soll sein Deposit so verteilt werden:
When an attacker is forfeited, their deposit will be distributed as follows:
| EmpfängerRecipient | % | WarumWhy | QPI |
|---|---|---|---|
| 🔥 VerbrennenBurn | 35% | Bestrafungssignal — Token dauerhaft vernichtet, deflationärPunishment signal — tokens permanently destroyed, deflationary | qpi.burn() |
| 🛡️ Angegriffener Dienst (Operator)Attacked service (operator) | 40% | Direkte Entschädigung des Opfers — größter EinzelanteilDirect compensation for the victim — largest single share | qpi.transfer(operator_) |
| 🏛️ Aktionäre (Shareholders)Shareholders | 20% | Passives Einkommen für IPO-Investoren — ähnlich wie QSwap (16%)Passive income for IPO investors — similar to QSwap (16%) | qpi.distributeDividends() |
| ⚙️ PlattformPlatform | 5% | Entwicklung & Betrieb — nachhaltig ohne InteressenskonfliktDevelopment & operations — sustainable without conflict of interest | qpi.transfer(platform_) |
END_EPOCH (~wöchentlich), kein manueller Aufruf nötig.
Design decision: Comparable to QSwap (16% shareholders). The platform share (5%) keeps development sustainable — but the model does not depend on attacks happening. No attack = no forfeit = no distribution. Distribution runs automatically in END_EPOCH (~weekly), no manual call needed.
// Called automatically once per epoch (~weekly)
END_EPOCH
{
if (totalForfeited == 0) { return; }
sint64 toBurn = div(pending * 35, 100);
sint64 toOperator = div(pending * 40, 100);
sint64 toShareholders = div(pending * 20, 100);
sint64 toPlatform = div(pending * 5, 100);
qpi.burn(toBurn); // 35% — permanently destroyed (deflationary)
qpi.transfer(operator_, toOperator); // 40% — compensation for the attacked service
qpi.distributeDividends(toShareholders); // 20% — passive income for IPO shareholders
qpi.transfer(platform_, toPlatform); // 5% — development & operations
totalForfeited = 0;
}
🔑 Token-Entropie — Was ist ein Struct? Token Entropy — What is a Struct?
Was ist ein Struct?What is a Struct?
Ein Struct (kurz für "Structure") ist ein Behälter für mehrere zusammengehörige Werte — wie eine Kiste mit beschrifteten Fächern. Alle Felder liegen hintereinander im Speicher.
A struct (short for "structure") is a container for multiple related values — like a box with labelled compartments. All fields are stored consecutively in memory.
struct TokenSeed
{
id owner; // Fach 1: Wallet-Adresse (32 Bytes)slot 1: wallet address (32 bytes)
uint32 tick; // Fach 2: Zeitstempel (4 Bytes)slot 2: timestamp (4 bytes)
sint64 slotIndex; // Fach 3: Array-Position (8 Bytes)slot 3: array position (8 bytes)
};
// → 44 Bytes am Stück im Speicher→ 44 bytes in one block in memory
qpi.K12() nimmt genau einen Parameter. Ein Struct bündelt mehrere Werte zu einem Block — K12 hasht dann alle 44 Bytes auf einmal. Kein String-Zusammenkleben nötig.
qpi.K12() takes exactly one parameter. A struct bundles multiple values into one block — K12 then hashes all 44 bytes at once. No string concatenation needed.
Warum K12(owner) allein nicht reichtWhy K12(owner) alone is not enough
qpi.K12(caller)
// gleiche Wallet → immer gleicher Token
// Kollision bei gleichem Tick!
qpi.K12(seed) // owner+tick+slot
// alle drei müssen gleichzeitig
// kollidieren → praktisch unmöglich
| QuelleSource | Kollidiert wenn...Collides when... |
|---|---|
owner | gleiche Wallet — gewollt, aber allein nicht genugsame wallet — intended, but not enough alone |
tick | exakt gleicher Tick (½ Sekunde) — sehr unwahrscheinlichexact same tick (½ second) — very unlikely |
slotIndex | gleicher freier Slot — unmöglich wenn owner gleichsame free slot — impossible if owner is same |
🔌 TypeScript SDK
Warum ein SDK?Why an SDK?
Der Mock-Server verwaltet Deposits in einem In-Memory-Array. Für echtes Qubic brauchen wir HTTP-Requests an einen RPC-Node. Das offizielle Paket @qubic-lib/qubic-ts-library v0.1.6 stellt Klassen bereit, um Transaktionen zu bauen, zu signieren und zu broadcasten.The mock server stores deposits in an in-memory array. For real Qubic we need HTTP requests to an RPC node. The official package @qubic-lib/qubic-ts-library v0.1.6 provides classes to build, sign, and broadcast transactions.
Zwei Arten von SC-AufrufenTwo types of SC calls
| TypType | HTTP | Signatur?Signature? | Sofortiges Ergebnis?Immediate result? |
|---|---|---|---|
| Procedure | POST /v1/broadcast-transaction | ✅ Ja | ❌ Nein — nach TickNo — after tick |
| Function | POST /v1/querySmartContract | ❌ Nein | ✅ SofortImmediate |
Procedure: TX bauen, signieren, broadcastenProcedure: build, sign, broadcast TX
const tx = new QubicTransaction()
.setSourcePublicKey(new PublicKey(senderPublicId))
.setDestinationPublicKey(new PublicKey(CONTRACT_ADDRESS))
.setAmount(new Long(Number(amount)))
.setTick(currentTick + 15) // TX valid for next ~15 ticks
.setInputType(IDX_DEPOSIT)
.setPayload(dynPayload);
const built = await tx.build(senderSeed);
const encoded = tx.encodeTransactionToBase64(built);
await fetch('/v1/broadcast-transaction', {
method: 'POST',
body: JSON.stringify({ encodedTransaction: encoded }),
});
Wichtige Einschränkung: kein synchroner Procedure-OutputImportant limitation: no synchronous procedure output
broadcast-transaction liefert nur eine Eingangsbestätigung — nicht das SC-Ergebnis. Ablauf: TX broadcasten → txId erhalten → warten → validateSession() pollen.broadcast-transaction returns only a submission acknowledgement — not the SC result. Flow: broadcast TX → receive txId → wait → poll validateSession().
Function: Base64-Query
await fetch('/v1/querySmartContract', {
method: 'POST',
body: JSON.stringify({
contractIndex: CONTRACT_INDEX,
inputType: IDX_VALIDATE_SESSION,
inputSize: 32,
requestData: toBase64(token), // 32-byte token as Base64
}),
});
// Response: { responseData: "<base64>" }
// → fromBase64() → Uint8Array → readUint32LE()
Struct-Größen: exakte Übereinstimmung mit QubicShield.hStruct sizes: must match QubicShield.h exactly
const SIZE_DEPOSIT_INPUT = 8; // sint64
const SIZE_REFUND_INPUT = 36; // uint32 + id
const SIZE_VALIDATE_INPUT = 32; // id
const SIZE_VALIDATE_OUT = 45; // uint8+uint32+uint32+id+uint32
const SIZE_STATS_OUT = 32; // uint32+uint32+sint64*3
USE_REAL_SC Env-Varenv var
# Mock (default)
npm run dev
# Real SC (needs CONTRACT_ADDRESS + CONTRACT_INDEX in .env)
USE_REAL_SC=true npm run dev
Beim Start zeigt der Server: Backend: 🔗 Qubic SC (real) oder Backend: 🧪 In-Memory Mock.On startup the server shows: Backend: 🔗 Qubic SC (real) or Backend: 🧪 In-Memory Mock.
▶ Demo starten & API Tester bedienen Running the Demo & Using the API Tester
Der QubicShield-Server ist ein einzelner Express-Prozess, der gleichzeitig das Frontend ausliefert und die API-Endpunkte bereitstellt. Es gibt keinen separaten Frontend-Server.
Server starten
cd qubicshield
npm run dev
Die Konsole zeigt dann:
QubicShield Demo Server running at http://localhost:3000
Backend: 🧪 In-Memory Mock
Routes:
POST /api/deposit – create deposit
GET /api/validate/:token – validate token
POST /api/refund – refund deposit
GET /api/protected – access protected resource
GET /api/stats – system statistics
GET / – demo UI
Was du siehst
Der Server liefert drei Seiten unter http://localhost:3000:
| URL | Seite | Beschreibung |
|---|---|---|
/ |
API Tester | Interaktive Demo: Deposit erstellen, Token validieren, Protected Resource aufrufen, Angriff simulieren, Live-Statistiken. Tabs für jeden Schritt. |
/demo.html |
Simulation | Vollautomatischer Ablauf mit Stepper-Anzeige und Aktivitäts-Log. Wähle eine Ziel-URL (erreichbar → Refund, nicht erreichbar → DDoS-Forfeit). Browser-Vorschau zeigt HTTP-Status. |
/dashboard.html |
Dashboard | Live-Übersicht: Stat-Karten (Deposits, Sessions, gebundene QU, Forfeits), Forfeit-Split-Balken, aktive Sessions-Tabelle, Event-Log mit allen Ereignissen. Aktualisiert alle 8 Sekunden. |
Alle Seiten: DE / EN Sprachumschalter oben rechts · grüner Punkt = Server online · Navigation zwischen den Seiten über die Buttons Home / Simulation / Dashboard.
Typischer Ablauf
- Deposit erstellen — beliebige Wallet-Adresse eingeben, Betrag wählen, Button klicken. Server gibt
sessionId+accessTokenzurück — werden automatisch in alle anderen Felder übertragen. - Token validieren — prüft ob der Token noch gültig ist (Ablaufzeit, Request-Zähler).
- Protected Resource aufrufen — simuliert einen API-Aufruf unter dieser Session. Bei zu vielen Anfragen wird der Deposit einbehalten (DDoS-Erkennung).
- Refund anfordern — gibt den Deposit zurück, solange kein Angriffsmuster erkannt wurde.
- Stats abrufen — Übersicht aller Deposits, Forfeits und Gesamtzahlen.
Jede Karte zeigt außerdem den äquivalenten curl-Befehl zum Aufklappen — nach einem Deposit werden die echten Werte automatisch eingesetzt.
Grundprinzip
1 Deposit = 1 Session = 1 Token. Das ist die Sicherheitslogik — jeder API-Zugriff hat einen eigenen Einsatz. Mehrmals auf „Deposit erstellen" klicken erzeugt mehrere unabhängige Sessions mit je eigenem Token und heldAmount. Ein Token verfällt nach 30 Minuten — danach ist ein neuer Deposit nötig.
Mock-Modus vs. echter SC
Standardmäßig läuft alles im Mock-Modus — kein echtes Wallet nötig, Deposits leben nur im RAM (weg bei Neustart). Für den echten Smart Contract:
USE_REAL_SC=true npm run dev
The QubicShield server is a single Express process that simultaneously serves the frontend and provides the API endpoints. There is no separate frontend server.
Starting the server
cd qubicshield
npm run dev
The console will show:
QubicShield Demo Server running at http://localhost:3000
Backend: 🧪 In-Memory Mock
Routes:
POST /api/deposit – create deposit
GET /api/validate/:token – validate token
POST /api/refund – refund deposit
GET /api/protected – access protected resource
GET /api/stats – system statistics
GET / – demo UI
What you see
The server serves three pages under http://localhost:3000:
| URL | Page | Description |
|---|---|---|
/ |
API Tester | Interactive demo: create deposit, validate token, call protected resource, simulate attack, view live statistics. One tab per step. |
/demo.html |
Simulation | Fully automated flow with stepper display and activity log. Choose a target URL (reachable → refund, unreachable → DDoS forfeit). Browser preview shows the HTTP status. |
/dashboard.html |
Dashboard | Live overview: stat cards (deposits, sessions, held QU, forfeits), forfeit split bar, active sessions table, event log with all events. Auto-refreshes every 8 seconds. |
All pages: DE / EN language toggle top right · green dot = server online · navigation between pages via the Home / Simulation / Dashboard buttons.
Typical workflow
- Create deposit — enter any wallet address, choose an amount, click the button. The server returns a
sessionId+accessToken— auto-filled into all other fields. - Validate token — checks if the token is still valid (expiry time, request counter).
- Call protected resource — simulates an API call under this session. Too many requests trigger DDoS detection and the deposit is forfeited.
- Request refund — returns the deposit as long as no attack pattern was detected.
- Fetch stats — overview of all deposits, forfeits, and totals.
Each card also shows the equivalent curl command — click to expand. After a deposit, the real values are automatically substituted.
Core principle
1 Deposit = 1 Session = 1 Token. This is the security logic — every API access has its own stake. Clicking "Create Deposit" multiple times creates multiple independent sessions, each with its own token and heldAmount. A token expires after 30 minutes — a new deposit is required after that.
Mock mode vs. real SC
By default everything runs in mock mode — no real wallet needed, deposits live in RAM only (gone on restart). To use the real smart contract:
USE_REAL_SC=true npm run dev
🛡 DDoS-Erkennung & Deposit-Forfeit DDoS Detection & Deposit Forfeit
QubicShield erkennt Angriffsmuster automatisch in zwei unabhängigen Schichten und behält den Deposit des Angreifers ein (forfeit). Der Angreifer verliert sein Geld — der Dienst bleibt verfügbar.
Schicht 1 — Rate Limiter Middleware
Datei: src/middleware/rateLimiter.ts
Implementiert ein Sliding Window pro Access-Token:
- Fenster: 60 Sekunden
- Schwellwert: 50 Anfragen → setzt
isAttacking = true - Setzt Response-Header:
X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Window - Blockt nicht sofort — gibt dem Route-Handler die Möglichkeit, den Deposit zuerst einzubehalten
Schicht 2 — DepositManager
Datei: src/depositManager.ts → Methode isAttacking()
Zweiter unabhängiger Zähler mit eigenem Schwellwert:
- Fenster: 60 Sekunden
- Schwellwert: 100 Anfragen → Deposit wird einbehalten
- Wird auch beim Refund-Versuch geprüft — wer angreift und dann Geld zurückfordert, verliert den Deposit trotzdem
Ablauf bei erkanntem Angriff
1. Request kommt rein → rateLimiter prüft (50 req/min)
2. Route: depositManager.isAttacking() prüft (100 req/min)
3. Bei Überschreitung → forfeitDeposit() → Deposit einbehalten
4. HTTP 403: "Attack pattern detected. Deposit has been forfeited."
Live-Demo im API Tester
Im API Tester (localhost:3000) gibt es die Karte „DDoS-Angriff simulieren". Ein Klick auf „Angriff starten" sendet automatisch 110 Anfragen in schneller Folge. Ein Fortschrittsbalken zeigt den Verlauf — sobald der Server den Angriff erkennt (typisch nach ~100 Anfragen), wird der Deposit einbehalten und der Balken springt auf rot.
QubicShield automatically detects attack patterns in two independent layers and forfeits the attacker's deposit. The attacker loses their money — the service stays available.
Layer 1 — Rate Limiter Middleware
File: src/middleware/rateLimiter.ts
Implements a sliding window per access token:
- Window: 60 seconds
- Threshold: 50 requests → sets
isAttacking = true - Sets response headers:
X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Window - Does not block immediately — lets the route handler forfeit the deposit first
Layer 2 — DepositManager
File: src/depositManager.ts → method isAttacking()
Second independent counter with its own threshold:
- Window: 60 seconds
- Threshold: 100 requests → deposit is forfeited
- Also checked on refund attempts — an attacker trying to reclaim their deposit still loses it
Flow when attack is detected
1. Request arrives → rateLimiter checks (50 req/min)
2. Route: depositManager.isAttacking() checks (100 req/min)
3. Threshold exceeded → forfeitDeposit() → deposit held
4. HTTP 403: "Attack pattern detected. Deposit has been forfeited."
Live demo in the API Tester
The API Tester at localhost:3000 has a card "Simulate DDoS Attack". One click on "Start Attack" automatically fires 110 requests in rapid succession. A progress bar shows the progress — once the server detects the attack (typically after ~100 requests), the deposit is forfeited and the bar turns red.
🚀 Nächste Schritte Next Steps
⚠️ Offene Probleme & bekannte Einschränkungen Open Issues & Known Limitations
Diese Punkte wurden bei der Analyse des PoC identifiziert. Sie sind kein Blocker für das Konzept, müssen aber vor einem produktiven Einsatz gelöst werden. These issues were identified during PoC analysis. They are not blockers for the concept, but must be resolved before production use.
Das Qubic-Netzwerk startet wöchentlich neu. Der Ausfall kann bis zu 45 Minuten dauern. Die aktuelle Retry-Logik in
scClient.ts deckt nur 5 × 8 s = 40 Sekunden ab.
Während des Ausfalls gibt der Server 502-Fehler zurück — kein Fallback-Modus ist implementiert.
Das Qubic-Team arbeitet an einer Lösung.
The Qubic network restarts weekly. The outage can last up to 45 minutes.
The current retry logic in scClient.ts covers only 5 × 8 s = 40 seconds.
During the full outage window the server returns 502 errors — no degraded-mode fallback is implemented.
The Qubic team is working on a fix.
Qubic-Ticks produzieren gelegentlich keinen Block. Eine Transaktion, die in so einen Tick gesendet wird, wird stillschweigend verworfen.
scClient.ts erkennt veraltete Ticks und wiederholt den Versuch bis zu 5-mal — aber der Client
erhält nur eine txId zurück und muss /api/validate/<token> manuell pollen.
Wenn alle Versuche in leeren Ticks landen, bleibt der Deposit unbestätigt.
Qubic ticks occasionally produce no block. A transaction broadcast to such a tick is silently discarded.
scClient.ts detects stale ticks and retries up to 5 times — but the client only
receives a txId and must manually poll /api/validate/<token>.
If all retries land in empty ticks, the deposit never confirms.
Die Angriffserkennung läuft lokal im Server. Wenn ein Angriff erkannt wird, wird
depositManager.forfeitDeposit() aufgerufen — das betrifft aber nur den Mock-Zustand.
Die Forfeit()-Procedure im Smart Contract wird nie aufgerufen.
Im SC-Modus behält ein Angreifer seine Kaution on-chain.
Muss vor dem Testnet-Deploy implementiert werden.
Attack detection runs locally in the server. When an attack is detected,
depositManager.forfeitDeposit() is called — but this only updates mock state.
The Forfeit() procedure on the smart contract is never invoked.
In SC mode an attacker keeps their deposit on-chain.
Must be implemented before a meaningful testnet test.
PoC-Wert. Wenn alle Slots belegt sind, werden neue Deposits mit einem kryptischen Fehlercode abgelehnt. Kein Warteschlangen-Mechanismus, keine Überlaufstrategie und keine benutzerfreundliche Fehlermeldung vorhanden. Für Produktion erhöhen und Überlaufverhalten definieren. PoC value. When all slots are occupied, new deposits are rejected with a cryptic error code. No queue, overflow strategy, or user-facing message is implemented. Raise for production and define overflow behavior.
Ein legitimer Nutzer könnte durch aggressives Browser-Polling, Monitoring-Tools oder eine falsch kalibrierte Schwelle als Angreifer erkannt werden und seine Kaution verlieren. Es gibt keinen dokumentierten Einspruchs- oder Rückerstattungsprozess für solche Fälle. A legitimate user could be flagged as an attacker due to aggressive browser polling, monitoring tools, or a miscalibrated threshold and lose their deposit. No dispute or appeal process is documented for such cases.
GET /api/events funktioniert nur im Mock. Im echten SC-Modus gibt es keinen
Audit-Trail für Deposits, Rückerstattungen oder Konfiszierungen — das Dashboard zeigt dann keine Events.
GET /api/events only works in mock mode. In real SC mode there is no
audit trail for deposits, refunds, or forfeit decisions — the dashboard shows no events.