JP2
world 🌍
Wróć do PPC

Draft Kontraktu (Dla Ciebie, Peter)

Witaj, Peter. Skoro kliknąłeś w ukryty link, oznacza to, że nie wystarczają Ci hand-wavy opisy tokenomiki dla zwykłych użytkowników. Wolisz zobaczyć, jak to będzie wyglądać pod maską. Zgadzam się z Tobą w pełni — czytając zwykły tekst i tak trzeba rozumieć, jak działa kod, więc lepiej od razu pokazać draft.

Poniżej znajdziesz roboczy zarys kontraktu PapaCoin (PPC) w Solidity z technicznymi komentarzami. Główny cel jest jeden: brak funkcji mint() po fazie generacji (Genesis).

1. Definicja i Stała Podaż

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

/**
 * @title PapaCoin (PPC)
 * @dev Peter, zwróć uwagę, że dziedziczymy tylko po ERC20 i Ownable.
 * Nie korzystamy z ERC20Mintable, ponieważ całkowita podaż jest
 * wybijana tylko raz, w momencie publikacji kontraktu (lub poprzez 
 * dokładnie zaplanowany i zamknięty kontrakt dystrybucyjny).
 */
contract PapaCoin is ERC20, Ownable {
    // 21,370,000,000 PPC (21.37 mld) to całkowity, niezmienny hard cap
    uint256 public constant MAX_SUPPLY = 21370000000 * 10 ** 18;

    /**
     * @dev Konstruktor wywołany tylko raz podczas deploymentu.
     * Mincimy od od razu całą pulę do portfeli lub kontraktów skarbcowych
     * z przeznaczeniem na snapshot (Airdrop) + pule płynności (DEX).
     */
    constructor() ERC20("PapaCoin", "PPC") Ownable(msg.sender) {
        // Wszystkie tokeny trafiają natychmiast do deployera (lub do
        // smart contractu zajmującego się dystrybucją airdropów/snapshotu).
        _mint(msg.sender, MAX_SUPPLY);
    }
}

2. Brak backdoora do dodruku

Peter, kluczowe jest to, czego w tym kontrakcie nie ma. Często projekty zostawiają furtkę dla właściciela w taki sposób:

// TAKIEGO KODU U NAS NIE ZNAJDZIESZ:
function mint(address to, uint256 amount) public onlyOwner {
    _mint(to, amount);
}

Nasz kontrakt korzysta ze standardowej implementacji z OpenZeppelin. Ponieważ w naszym kontrakcie PapaCoin nie zdefiniowaliśmy żadnej publicznej ani zewnętrznej funkcji wywołującej wewnętrzne _mint(), EVM fizycznie uniemożliwia stworzenie nowych tokenów. Nikt, nawet adres widniejący jako owner(), nie ma technicznej możliwości zwiększenia puli. totalSupply() z OpenZeppelin pozostanie niezmienne (lub może jedynie maleć, jeśli zaimplementujemy burning).

3. Dystrybucja Snapshotu (Claim Contract)

Skoro wszystkie tokeny zostały wybite w momencie powstania kontraktu, jak trafią do użytkowników z bazy danych PapaHunt? Przygotujemy oddzielny kontrakt typu Merkle Airdrop, który będzie przetrzymywał zablokowaną pulę przeznaczoną dla łowców.

import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";

contract PapaClaim {
    IERC20 public immutable papaCoin;
    bytes32 public immutable merkleRoot; // Hash zawierający stan sald offline
    
    mapping(address => bool) public hasClaimed;

    constructor(address _papaCoin, bytes32 _merkleRoot) {
        papaCoin = IERC20(_papaCoin);
        merkleRoot = _merkleRoot;
    }

    /**
     * @dev Każdy gracz z PapaHunt weryfikuje swoje historyczne
     * saldo za pomocą kryptograficznego dowodu Merkle'a.
     */
    function claim(uint256 amount, bytes32[] calldata merkleProof) external {
        require(!hasClaimed[msg.sender], "Juz odebrano!");
        
        // Weryfikacja: tylko zapisane w bazie off-chain salda przejdą ten test
        bytes32 node = keccak256(abi.encodePacked(msg.sender, amount));
        require(MerkleProof.verify(merkleProof, merkleRoot, node), "Bledny dowod!");

        hasClaimed[msg.sender] = true;
        
        // Transfer wygenerowanych na samym poczatku tokenów
        require(papaCoin.transfer(msg.sender, amount), "Transfer failed");
    }
}

Mam nadzieję, Peter, że ten kod wyjaśnia całą architekturę i zamyka temat „skąd się biorą monety”. Cały proces opiera się o sprawdzoną kryptografię (Merkle Trees) i publiczne niezmienne kontrakty. Użytkownik ufa matematyce, a nie adminowi.