Eine Prägemaschine für Token auf Base, gebaut als Three.js-Bewegtbild.
Du schickst ETH an eine Einzahlungsadresse, und der Server deployt daraufhin einen ERC-20 mit fester Menge, eröffnet dafür einen Uniswap-V3-Pool, füllt diesen Pool mit der Einzahlung und der gesamten Token-Menge und verbrennt anschließend die LP-Position, damit die Liquidität nie wieder herausgezogen werden kann. Die Oberfläche ist eine Pfefferminz-Schmiede, die auf jede Stufe dieser Pipeline in Echtzeit reagiert.
Nichts wird an eine fremde Oberfläche weitergereicht — die Uniswap-Contracts werden direkt vom Server aufgerufen.
HTML · CSS · reines JS · Three.js (GLSL) · Node/Express · ethers v6 · solc
npm install # kompiliert dabei auch den Solidity-Contract
cp .env.example .env # danach MNEMONIC eintragen (siehe unten)
npm start # → http://localhost:3000Es gibt keinen Demo- oder Simulationsmodus. Die App spricht immer mit einer echten Kette, und ohne Betreiber-Seed startet sie nicht:
node -e "console.log(require('ethers').Wallet.createRandom().mnemonic.phrase)"Dieser Seed braucht kein Guthaben. Jeder Auftrag leitet daraus seine eigene Einzahlungs-Wallet ab, und das Gas eines Auftrags bezahlt die Einzahlung selbst.
Base Mainnet und Base Sepolia laufen im selben Prozess. Der Besucher wählt
das Netzwerk mit dem Schalter im Seitenkopf; ein Auftrag merkt sich die Kette,
für die er angelegt wurde, und Provider, Einzahlungs-Wallet und Explorer-Links
folgen daraus. DEFAULT_CHAIN_ID entscheidet nur, wo die Seite startet — auf
dem Testnetz, damit niemand beim ersten Besuch einen Klick von echtem ETH
entfernt ist.
Die Oberfläche ist durchgehend deutsch. Im Code bleiben Bezeichner englisch.
| # | Schritt | Was tatsächlich passiert |
|---|---|---|
| 1 | Auftrag angelegt | Aus dem Betreiber-Seed wird eine Wallet unter m/44'/60'/0'/0/<index> abgeleitet. Jede Schmiede bekommt ihre eigene Adresse — Einzahlungen werden nie vermischt, und ein gescheiterter Auftrag kommt an das Geld eines anderen nicht heran. |
| 2 | Einzahlung erkannt | Ein Poller beobachtet diese Adresse. Einfache ETH-Überweisungen an eine EOA erzeugen keine Logs, deshalb ist Abfragen (und nicht das Abonnieren von Logs) hier das richtige Mittel — und es funktioniert mit jedem Base-RPC. |
| 3 | Token gegossen | PeppermintToken wird aus der Auftrags-Wallet deployt. Feste Menge, kein Eigentümer, kein mint(), keine Pause, keine Transfer-Hooks. |
| 4 | Pool eröffnet | Der Liquiditätsanteil der Einzahlung wird zu WETH gewickelt, dann initialisiert NonfungiblePositionManager.createAndInitializePoolIfNecessary das Paar auf genau das Verhältnis, das wir gleich einfüllen. |
| 5 | Liquidität eingefüllt | mint() eröffnet eine Position über die gesamte Kursspanne der gewählten Gebührenstufe. |
| 6 | Gesperrt und ausgefegt | Die LP-NFT geht an 0x…dEaD (einstellbar), die Protokollgebühr wird weitergeleitet, und das übrige Gasgeld geht an den Ersteller zurück. |
Der Token-Contract wird Sekunden vor dem Pool deployt, das Paar kann also
noch nicht existieren, und niemand kann darin gehandelt haben. Der Pool wird in
derselben Pipeline auf unser exaktes Verhältnis initialisiert — deshalb dürfen
amount0Min und amount1Min gefahrlos 0 sein. Es gibt zu diesem Zeitpunkt
noch keinen Kurs, gegen den sich jemand stellen könnte.
sqrtPriceX96 = sqrt(amount1 / amount0) · 2^96, vollständig in BigInt
gerechnet über eine ganzzahlige Newton-Wurzel (server/chain.js). In der Nähe
des Poolkurses kommt keine Fließkommazahl vor.
Öffentliche RPC-Endpunkte sind lastverteilt: der Knoten, der eth_estimateGas
beantwortet, ist nicht zwingend derselbe, der unseren Deploy geschürft hat. Ein
Knoten, der diesen Block noch nicht hat, sieht an der Adresse keinen Code,
schätzt den Aufruf auf den Grundpreis — und die Transaktion bleibt dann ohne
Gas liegen, sobald sie auf den echten Contract trifft. Gas wird nach Verbrauch
abgerechnet, die Einzahlung wäre also für nichts verbrannt.
Deshalb wartet die Pipeline nach dem Deploy und nach der Pool-Eröffnung, bis der Endpunkt das Neugeschaffene wirklich sieht, und jeder Contract-Aufruf geht mit 30 % Luft über der Schätzung hinaus. Ungenutzte Reserve kostet nichts.
Alles steht in .env — die kommentierte Liste findest du in .env.example.
| Variable | Vorgabe | Bedeutung |
|---|---|---|
DEFAULT_CHAIN_ID |
84532 |
Auf welchem Netzwerk die Seite startet. Beide sind immer verfügbar |
RPC_URL_<chainId> |
öffentliches Base-RPC | Eine pro Kette, z. B. RPC_URL_8453 |
MNEMONIC |
— | Pflicht. Betreiber-Seed für alle Auftrags-Wallets; ohne ihn startet die App nicht |
UNISWAP_FEE |
10000 |
V3-Gebührenstufe: 500 / 3000 / 10000 |
LIQUIDITY_SHARE |
0.80 |
Anteil der Einzahlung, der zu Liquidität wird |
LOCK_LIQUIDITY |
true |
LP-NFT verbrennen statt aushändigen |
PROTOCOL_FEE_PERCENT |
0 |
Optionaler Anteil, braucht PROTOCOL_FEE_RECIPIENT |
MIN_DEPOSIT_ETH |
0.005 |
Darunter wird eine Einzahlung ignoriert |
Die Contract-Adressen für Base und Base Sepolia sind eingebaut und lassen sich
pro Kette überschreiben, indem du die Chain-ID anhängst: WETH9_84532,
UNISWAP_V3_FACTORY_84532, UNISWAP_V3_POSITION_MANAGER_84532.
Testnetz-Adressen wandern. Die Uniswap-V3-Adressen auf Base Mainnet sind die bekannten kanonischen. Wenn ein Aufruf auf Base Sepolia mit „no contract" abbricht, sieh in der Uniswap-Dokumentation nach der aktuellen Deployment-Liste und überschreibe sie per Umgebungsvariable — genau dafür sind diese Überschreibungen da.
| Datei | Aufgabe |
|---|---|
public/js/scene.js |
Die Maschine: Sockel, Messingschild, Zuckerstangen-Säulen, Kreiselringe, glühender Kern, Glaskuppel, Trichter, Zahnräder, Ausguss, Poolbecken, die geprägte Münze. Die Kamera-Keyframes hängen am Seiten-Scroll. |
public/js/shaders.js |
Von Hand geschriebenes GLSL — Pfefferminz-Spiralstreifen, fbm-Glutkern, Bodenringe, additive Funken, Liquiditätsstrom und ein Nachbearbeitungsdurchgang mit radialer Farbsäumung, Scanlines und Vignette. |
public/js/effects.js |
Die 2D-Ebene: Scroll-Einblendungen, Bonbonpapier-Buttons, magnetischer Hover, Klick-Wellen, Pfefferminz-Partikel, Konfetti. |
public/js/main.js |
Verbindet die SSE-Stufen der Pipeline mit den Stimmungen der Maschine. |
Die Maschine springt nie zwischen Zuständen. Jede animierte Größe — Hitze,
Drehzahl, Funkenenergie, Fluss, Bodenpuls — wird auf einen Zielwert pro Zustand
gedämpft, damit idle → charging → forging → pouring → complete wie
anlaufende Mechanik wirkt und nicht wie ein Schnitt.
prefers-reduced-motion wird überall beachtet: Parallaxe, Partikel, Konfetti
und Wackeln schalten sich ab.
Für die Dauer einer Prägung ist das hier eine verwahrende Konstruktion. Sei dir darüber im Klaren:
- Der Server hält einen Seed, der jede Einzahlungsadresse kontrolliert. Behandle
ihn wie eine heiße Wallet.
.envniemals committen (die Datei ist in.gitignore). - Wer deinen Endpunkt
/api/jobserreicht, kann eine Einzahlungsadresse erzeugen. Setz ein Rate-Limit und einen Reverse-Proxy davor, bevor das öffentlich erreichbar wird. data/jobs.jsonist eine flache Datei. Für kleine Mengen reicht das, eine echte Datenbank mit Sicherungen ersetzt sie nicht.- Einen Token zu deployen und einen Pool zu füllen kostet echtes Geld und ist nicht umkehrbar.
- Der mitgelieferte ERC-20 ist bewusst minimal und nicht auditiert. Lies
contracts/PeppermintToken.sol— es sind 90 Zeilen — bevor du ihm etwas anvertraust, das dir wichtig ist.
contracts/PeppermintToken.sol ERC-20 mit fester Menge, ohne Eigentümer
scripts/compile.js solc-js → artifacts/ (läuft bei npm install)
server/
config.js Umgebung + Contract-Adressen pro Kette
chain.js Provider, HD-Wallets, ABIs, V3-Kursrechnung
pipeline.js die Schmiede: Deploy → Pool → Liquidität → Ausfegen
watcher.js Abfrage der Einzahlungen
store.js Auftragsspeicher + Ereignisbus
index.js REST + SSE + Auslieferung der Oberfläche
public/ die Oberfläche
MIT — siehe LICENSE.