# Stablecoin Example

A production-ready stablecoin implementation with role-based access control, pausability, blacklist functionality, and minter allowances.

## Overview

This example demonstrates:
- Role-based access control (Admin, Minter, Pauser, Blacklister)
- Pausable transfers
- Address blacklisting
- Minter allowances with supply caps
- Decorators for ABI generation
- Detailed event logging

## Role-Based Access Control

Roles use bit flags (powers of 2) for efficient storage and checking:

```mermaid
---
config:
  theme: dark
---
graph LR
    subgraph "Role Enum - Powers of 2"
        A["Role.ADMIN = 1<br/>(2^0 = 0001)"]
        B["Role.MINTER = 2<br/>(2^1 = 0010)"]
        C["Role.PAUSER = 4<br/>(2^2 = 0100)"]
        D["Role.BLACKLISTER = 8<br/>(2^3 = 1000)"]
    end

    subgraph "Example: Account with Admin + Minter"
        E["Roles = 0011 (binary)<br/>= 1 | 2 = 3"]
    end

    subgraph "Bitwise Operations"
        F["Grant Role: SafeMath.or(currentRoles, role)"]
        G["Revoke Role: SafeMath.and(currentRoles, SafeMath.xor(role, u256.Max))"]
        H["Check Role: !SafeMath.and(roles, role).isZero()"]
    end

    A --> E
    B --> E
    E --> F
    E --> G
    E --> H
```

### Role Implementation

```typescript
// Define roles as enum with bit flags
enum Role {
    ADMIN = 1,        // 2^0
    MINTER = 2,       // 2^1
    PAUSER = 4,       // 2^2
    BLACKLISTER = 8   // 2^3
}

// Check role before action
private onlyRole(role: u256): void {
    if (!this.hasRole(Blockchain.tx.sender, role)) {
        throw new Revert('AccessControl: missing role');
    }
}

public hasRole(account: Address, role: u256): bool {
    const roles = this._roles.get(account);
    return !SafeMath.and(roles, role).isZero();
}
```

**Solidity Comparison:**

```solidity
// Solidity - OpenZeppelin AccessControl
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

function hasRole(bytes32 role, address account) public view returns (bool) {
    return _roles[role].members[account];
}

modifier onlyRole(bytes32 role) {
    require(hasRole(role, msg.sender), "AccessControl: missing role");
    _;
}

// OP_NET - Bit flags for efficiency
enum Role { ADMIN = 1, MINTER = 2, PAUSER = 4, BLACKLISTER = 8 }

public hasRole(account: Address, role: u256): bool {
    const roles = this._roles.get(account);
    return !SafeMath.and(roles, role).isZero();
}
```

### Grant/Revoke Flow

```mermaid
---
config:
  theme: dark
---
flowchart LR
    A["👤 User submits grantRole TX"] --> B{Has ADMIN role?}
    B -->|No| C[Revert: Missing role]
    B -->|Yes| D[Read current roles]
    D --> E[OR roles together]
    E --> F[Write to storage]
    F --> G[Emit event]
    G --> H[TX Success]
```

### Role Hierarchy

```
Admin (Role.ADMIN = 1)
- Can grant/revoke all roles
- Can update master minter
- Has emergency powers

Master Minter
- Configure minter allowances
- Add new minters
- Remove minters

Minter (Role.MINTER = 2)
- Mint up to allowance
- Burn own tokens

Pauser (Role.PAUSER = 4)
- Pause all transfers
- Unpause

Blacklister (Role.BLACKLISTER = 8)
- Add addresses to blacklist
- Remove from blacklist
```

## Pausable Functionality

The contract can be paused to block all transfers:

```mermaid
---
config:
  theme: dark
---
stateDiagram-v2
    [*] --> Normal: Deploy Contract
    Normal --> Paused: pause() TX by Role.PAUSER
    Paused --> Normal: unpause() TX by Role.PAUSER

    state Normal {
        [*] --> AllowTransfers
        AllowTransfers --> AllowMinting
        AllowMinting --> AllowBurning
    }

    state Paused {
        [*] --> BlockTransfers
        BlockTransfers --> BlockMinting
        BlockMinting --> BlockBurning
    }

    note right of Normal
        All token operations allowed
        transfers, mints, burns work
        Storage updates permitted
    end note

    note right of Paused
        Only view functions work
        All state-changing ops revert
        Users must wait for unpause
    end note
```

### Pausable Implementation

```typescript
private whenNotPaused(): void {
    if (this._paused.value) {
        throw new Revert('Pausable: paused');
    }
}

@method()
@emit('Paused')
public pause(_calldata: Calldata): BytesWriter {
    this.onlyRole(u256.fromU64(Role.PAUSER));
    this._paused.value = true;
    this.emitEvent(new Paused(Blockchain.tx.sender));
    return new BytesWriter(0);
}
```

**Solidity Comparison:**

```solidity
// Solidity - OpenZeppelin Pausable
modifier whenNotPaused() {
    require(!paused(), "Pausable: paused");
    _;
}

function pause() external onlyRole(PAUSER_ROLE) {
    _pause();
}

// OP_NET
private whenNotPaused(): void {
    if (this._paused.value) {
        throw new Revert('Pausable: paused');
    }
}
```

## Blacklist System

Addresses can be blacklisted to prevent transfers. Each address exists in one of two states:

```mermaid
---
config:
  theme: dark
---
stateDiagram-v2
    [*] --> Active: Address First Used
    Active --> Blacklisted: blacklist(address) by BLACKLISTER
    Blacklisted --> Active: unBlacklist(address) by BLACKLISTER

    state Active {
        [*] --> CanTransfer
        CanTransfer --> CanReceive
        CanReceive --> CanMint
    }

    state Blacklisted {
        [*] --> TransferBlocked
        TransferBlocked --> ReceiveBlocked
        ReceiveBlocked --> MintBlocked
    }

    note right of Active
        Address can:
        - Send tokens
        - Receive tokens
        - Be minted to
    end note

    note right of Blacklisted
        Address cannot:
        - Send tokens (reverts)
        - Receive tokens (reverts)
        - Be minted to (reverts)
        Balance is frozen
    end note
```

```mermaid
---
config:
  theme: dark
---
flowchart LR
    A["👤 User submits transfer TX"] --> B{Contract paused?}
    B -->|Yes| C[Revert: Paused]
    B -->|No| D{Blacklisted?}
    D -->|Yes| E[Revert: Blacklisted]
    D -->|No| F{Sufficient balance?}
    F -->|No| G[Revert: Insufficient balance]
    F -->|Yes| H[Update balances]
    H --> I[Emit event]
    I --> J[TX Success]
```

### Blacklist Implementation

```typescript
private notBlacklisted(account: Address): void {
    // AddressMemoryMap.get() returns u256; non-zero means blacklisted
    if (!this._blacklist.get(account).isZero()) {
        throw new Revert('Blacklisted');
    }
}

public override transfer(calldata: Calldata): BytesWriter {
    this.whenNotPaused();
    this.notBlacklisted(Blockchain.tx.sender);

    const to = calldata.readAddress();
    this.notBlacklisted(to);

    // Continue with transfer...
}
```

## Minter Allowance System

Each minter has a limited supply they can mint. The master minter configures allowances for each minter:

```mermaid
sequenceDiagram
    participant MasterMinter as 👤 Master Minter
    participant BTC as Bitcoin Network
    participant Contract as Contract Execution
    participant Minter as 👤 Minter
    participant Storage as Storage Layer

    MasterMinter->>BTC: Submit configureMinter(minter, allowance) TX
    BTC->>Contract: Execute configureMinter
    Contract->>Contract: Check caller is master minter
    Contract->>Storage: Write Role.MINTER if needed
    Contract->>Storage: Write minter allowance
    Storage-->>Contract: Success
    Contract-->>BTC: Success
    BTC-->>MasterMinter: TX Confirmed

    Note over Minter,Storage: Later: Minter wants to mint

    Minter->>BTC: Submit mint(to, amount) TX
    BTC->>Contract: Execute mint
    Contract->>Contract: Check Role.MINTER
    Contract->>Contract: Check not paused
    Contract->>Contract: Check blacklist (to + minter)
    Contract->>Storage: Read minter allowance
    Storage-->>Contract: currentAllowance

    alt amount > allowance
        Contract-->>BTC: Revert: Allowance exceeded
        BTC-->>Minter: TX Failed
    else amount <= allowance
        Contract->>Storage: Write allowance -= amount
        Contract->>Storage: Write _mint(to, amount)
        Contract->>Contract: Emit Mint event
        Contract-->>BTC: Success
        BTC-->>Minter: TX Confirmed
    end

    Note over Contract,Storage: Allowance tracks remaining mint capacity
```

### Minter Allowance Implementation

```typescript
@method(
    { name: 'minter', type: ABIDataTypes.ADDRESS },
    { name: 'allowance', type: ABIDataTypes.UINT256 },
)
public configureMinter(calldata: Calldata): BytesWriter {
    this.onlyMasterMinter();
    const minter = calldata.readAddress();
    const allowance = calldata.readU256();  // Max they can mint
    // ...
}
```

## Complete Implementation

```typescript
import { u256 } from '@btc-vision/as-bignum/assembly';
import {
    OP20,
    OP20InitParameters,
    Blockchain,
    Address,
    Calldata,
    BytesWriter,
    SafeMath,
    Revert,
    NetEvent,
    StoredBoolean,
    StoredAddress,
    AddressMemoryMap,
    ABIDataTypes,
} from '@btc-vision/btc-runtime/runtime';

// Role enum - MUST be powers of 2 for bitwise operations
enum Role {
    ADMIN = 1,        // 2^0
    MINTER = 2,       // 2^1
    PAUSER = 4,       // 2^2
    BLACKLISTER = 8   // 2^3
}

// Custom events
class RoleGranted extends NetEvent {
    public constructor(
        public readonly role: u256,
        public readonly account: Address,
        public readonly sender: Address
    ) {
        super('RoleGranted');
    }

    protected override encodeData(writer: BytesWriter): void {
        writer.writeU256(this.role);
        writer.writeAddress(this.account);
        writer.writeAddress(this.sender);
    }
}

class RoleRevoked extends NetEvent {
    public constructor(
        public readonly role: u256,
        public readonly account: Address,
        public readonly sender: Address
    ) {
        super('RoleRevoked');
    }

    protected override encodeData(writer: BytesWriter): void {
        writer.writeU256(this.role);
        writer.writeAddress(this.account);
        writer.writeAddress(this.sender);
    }
}

class Blacklisted extends NetEvent {
    public constructor(public readonly account: Address) {
        super('Blacklisted');
    }

    protected override encodeData(writer: BytesWriter): void {
        writer.writeAddress(this.account);
    }
}

class UnBlacklisted extends NetEvent {
    public constructor(public readonly account: Address) {
        super('UnBlacklisted');
    }

    protected override encodeData(writer: BytesWriter): void {
        writer.writeAddress(this.account);
    }
}

class Paused extends NetEvent {
    public constructor(public readonly account: Address) {
        super('Paused');
    }

    protected override encodeData(writer: BytesWriter): void {
        writer.writeAddress(this.account);
    }
}

class Unpaused extends NetEvent {
    public constructor(public readonly account: Address) {
        super('Unpaused');
    }

    protected override encodeData(writer: BytesWriter): void {
        writer.writeAddress(this.account);
    }
}

@final
export class Stablecoin extends OP20 {
    // Access control storage
    private rolesPointer: u16 = Blockchain.nextPointer;
    private masterMinterPointer: u16 = Blockchain.nextPointer;

    // Pausable storage
    private pausedPointer: u16 = Blockchain.nextPointer;

    // Blacklist storage
    private blacklistPointer: u16 = Blockchain.nextPointer;

    // Minter allowances
    private minterAllowancePointer: u16 = Blockchain.nextPointer;

    // Stored values
    private _roles: AddressMemoryMap;
    private _masterMinter: StoredAddress;
    private _paused: StoredBoolean;
    private _blacklist: AddressMemoryMap;
    private _minterAllowance: AddressMemoryMap;

    public constructor() {
        super();

        this._roles = new AddressMemoryMap(this.rolesPointer);
        this._masterMinter = new StoredAddress(this.masterMinterPointer, Address.zero());
        this._paused = new StoredBoolean(this.pausedPointer, false);
        this._blacklist = new AddressMemoryMap(this.blacklistPointer);
        this._minterAllowance = new AddressMemoryMap(this.minterAllowancePointer);
    }

    public override onDeployment(calldata: Calldata): void {
        const name = calldata.readString();
        const symbol = calldata.readString();
        const admin = calldata.readAddress();
        const masterMinter = calldata.readAddress();

        // Initialize as stablecoin (no max supply, 6 decimals typical for USD)
        this.instantiate(new OP20InitParameters(
            u256.Max,  // No max supply
            6,         // USDC-style decimals
            name,
            symbol
        ));

        // Set up initial roles
        this._grantRole(admin, u256.fromU64(Role.ADMIN));
        this._grantRole(admin, u256.fromU64(Role.PAUSER));
        this._grantRole(admin, u256.fromU64(Role.BLACKLISTER));
        this._masterMinter.value = masterMinter;
    }

    // ============ MODIFIERS ============

    private onlyRole(role: u256): void {
        if (!this.hasRole(Blockchain.tx.sender, role)) {
            throw new Revert('AccessControl: missing role');
        }
    }

    private whenNotPaused(): void {
        if (this._paused.value) {
            throw new Revert('Pausable: paused');
        }
    }

    private notBlacklisted(account: Address): void {
        // AddressMemoryMap.get() returns u256; non-zero means blacklisted
        if (!this._blacklist.get(account).isZero()) {
            throw new Revert('Blacklisted');
        }
    }

    private onlyMasterMinter(): void {
        if (!Blockchain.tx.sender.equals(this._masterMinter.value)) {
            throw new Revert('Caller is not master minter');
        }
    }

    // ============ ROLE MANAGEMENT ============

    private _grantRole(account: Address, role: u256): void {
        const currentRoles = this._roles.get(account);
        // Use SafeMath.or for bitwise OR on u256
        const newRoles = SafeMath.or(currentRoles, role);
        this._roles.set(account, newRoles);

        this.emitEvent(new RoleGranted(role, account, Blockchain.tx.sender));
    }

    private _revokeRole(account: Address, role: u256): void {
        const currentRoles = this._roles.get(account);
        // Use SafeMath.xor to invert, then SafeMath.and to clear bits
        const invertedRole = SafeMath.xor(role, u256.Max);
        const newRoles = SafeMath.and(currentRoles, invertedRole);
        this._roles.set(account, newRoles);

        this.emitEvent(new RoleRevoked(role, account, Blockchain.tx.sender));
    }

    public hasRole(account: Address, role: u256): bool {
        const roles = this._roles.get(account);
        // Use SafeMath.and for bitwise AND on u256
        return !SafeMath.and(roles, role).isZero();
    }

    @method(
        { name: 'account', type: ABIDataTypes.ADDRESS },
        { name: 'role', type: ABIDataTypes.UINT256 },
    )
    @returns({ name: 'success', type: ABIDataTypes.BOOL })
    @emit('RoleGranted')
    public grantRole(calldata: Calldata): BytesWriter {
        this.onlyRole(u256.fromU64(Role.ADMIN));

        const account = calldata.readAddress();
        const role = calldata.readU256();

        this._grantRole(account, role);

        return new BytesWriter(0);
    }

    @method(
        { name: 'account', type: ABIDataTypes.ADDRESS },
        { name: 'role', type: ABIDataTypes.UINT256 },
    )
    @returns({ name: 'success', type: ABIDataTypes.BOOL })
    @emit('RoleRevoked')
    public revokeRole(calldata: Calldata): BytesWriter {
        this.onlyRole(u256.fromU64(Role.ADMIN));

        const account = calldata.readAddress();
        const role = calldata.readU256();

        this._revokeRole(account, role);

        return new BytesWriter(0);
    }

    // ============ MINTING ============

    @method(
        { name: 'minter', type: ABIDataTypes.ADDRESS },
        { name: 'allowance', type: ABIDataTypes.UINT256 },
    )
    @returns({ name: 'success', type: ABIDataTypes.BOOL })
    @emit('MinterConfigured')
    public configureMinter(calldata: Calldata): BytesWriter {
        this.onlyMasterMinter();

        const minter = calldata.readAddress();
        const allowance = calldata.readU256();

        // Grant minter role if new
        if (!this.hasRole(minter, u256.fromU64(Role.MINTER))) {
            this._grantRole(minter, u256.fromU64(Role.MINTER));
        }

        // Set allowance
        this._minterAllowance.set(minter, allowance);

        return new BytesWriter(0);
    }

    @method({ name: 'minter', type: ABIDataTypes.ADDRESS })
    @returns({ name: 'success', type: ABIDataTypes.BOOL })
    @emit('MinterRemoved')
    public removeMinter(calldata: Calldata): BytesWriter {
        this.onlyMasterMinter();

        const minter = calldata.readAddress();

        this._revokeRole(minter, u256.fromU64(Role.MINTER));
        this._minterAllowance.set(minter, u256.Zero);

        return new BytesWriter(0);
    }

    @method(
        { name: 'to', type: ABIDataTypes.ADDRESS },
        { name: 'amount', type: ABIDataTypes.UINT256 },
    )
    @returns({ name: 'success', type: ABIDataTypes.BOOL })
    @emit('Mint')
    public mint(calldata: Calldata): BytesWriter {
        this.onlyRole(u256.fromU64(Role.MINTER));
        this.whenNotPaused();

        const to = calldata.readAddress();
        const amount = calldata.readU256();
        const minter = Blockchain.tx.sender;

        // Check blacklist
        this.notBlacklisted(to);
        this.notBlacklisted(minter);

        // Check and update allowance
        const allowance = this._minterAllowance.get(minter);
        if (allowance < amount) {
            throw new Revert('Minter allowance exceeded');
        }
        this._minterAllowance.set(minter, SafeMath.sub(allowance, amount));

        // Mint
        this._mint(to, amount);

        return new BytesWriter(0);
    }

    @method({ name: 'amount', type: ABIDataTypes.UINT256 })
    @returns({ name: 'success', type: ABIDataTypes.BOOL })
    @emit('Burn')
    public burn(calldata: Calldata): BytesWriter {
        this.onlyRole(u256.fromU64(Role.MINTER));
        this.whenNotPaused();

        const amount = calldata.readU256();
        const burner = Blockchain.tx.sender;

        this.notBlacklisted(burner);

        this._burn(burner, amount);

        return new BytesWriter(0);
    }

    // ============ PAUSABLE ============

    @method()
    @returns({ name: 'success', type: ABIDataTypes.BOOL })
    @emit('Paused')
    public pause(_calldata: Calldata): BytesWriter {
        this.onlyRole(u256.fromU64(Role.PAUSER));

        this._paused.value = true;
        this.emitEvent(new Paused(Blockchain.tx.sender));

        return new BytesWriter(0);
    }

    @method()
    @returns({ name: 'success', type: ABIDataTypes.BOOL })
    @emit('Unpaused')
    public unpause(_calldata: Calldata): BytesWriter {
        this.onlyRole(u256.fromU64(Role.PAUSER));

        this._paused.value = false;
        this.emitEvent(new Unpaused(Blockchain.tx.sender));

        return new BytesWriter(0);
    }

    // ============ BLACKLIST ============

    @method({ name: 'account', type: ABIDataTypes.ADDRESS })
    @returns({ name: 'success', type: ABIDataTypes.BOOL })
    @emit('Blacklisted')
    public blacklist(calldata: Calldata): BytesWriter {
        this.onlyRole(u256.fromU64(Role.BLACKLISTER));

        const account = calldata.readAddress();
        // AddressMemoryMap stores u256; use u256.One for true
        this._blacklist.set(account, u256.One);

        this.emitEvent(new Blacklisted(account));

        return new BytesWriter(0);
    }

    @method({ name: 'account', type: ABIDataTypes.ADDRESS })
    @returns({ name: 'success', type: ABIDataTypes.BOOL })
    @emit('UnBlacklisted')
    public unBlacklist(calldata: Calldata): BytesWriter {
        this.onlyRole(u256.fromU64(Role.BLACKLISTER));

        const account = calldata.readAddress();
        // AddressMemoryMap stores u256; use u256.Zero for false
        this._blacklist.set(account, u256.Zero);

        this.emitEvent(new UnBlacklisted(account));

        return new BytesWriter(0);
    }

    // ============ OVERRIDE TRANSFERS ============

    @method(
        { name: 'to', type: ABIDataTypes.ADDRESS },
        { name: 'amount', type: ABIDataTypes.UINT256 },
    )
    @returns({ name: 'success', type: ABIDataTypes.BOOL })
    @emit('Transfer')
    public override transfer(calldata: Calldata): BytesWriter {
        this.whenNotPaused();
        this.notBlacklisted(Blockchain.tx.sender);

        const to = calldata.readAddress();
        this.notBlacklisted(to);

        // Re-read to pass to parent
        const fullCalldata = new Calldata(calldata.buffer);
        return super.transfer(fullCalldata);
    }

    @method(
        { name: 'from', type: ABIDataTypes.ADDRESS },
        { name: 'to', type: ABIDataTypes.ADDRESS },
        { name: 'amount', type: ABIDataTypes.UINT256 },
    )
    @returns({ name: 'success', type: ABIDataTypes.BOOL })
    @emit('Transfer')
    public override transferFrom(calldata: Calldata): BytesWriter {
        this.whenNotPaused();

        const from = calldata.readAddress();
        const to = calldata.readAddress();

        this.notBlacklisted(Blockchain.tx.sender);
        this.notBlacklisted(from);
        this.notBlacklisted(to);

        // Re-read to pass to parent
        const fullCalldata = new Calldata(calldata.buffer);
        return super.transferFrom(fullCalldata);
    }

    // ============ VIEW FUNCTIONS ============

    @method()
    @returns({ name: 'paused', type: ABIDataTypes.BOOL })
    public isPaused(_calldata: Calldata): BytesWriter {
        const writer = new BytesWriter(1);
        writer.writeBoolean(this._paused.value);
        return writer;
    }

    @method({ name: 'account', type: ABIDataTypes.ADDRESS })
    @returns({ name: 'blacklisted', type: ABIDataTypes.BOOL })
    public isBlacklisted(calldata: Calldata): BytesWriter {
        const account = calldata.readAddress();

        const writer = new BytesWriter(1);
        // AddressMemoryMap.get() returns u256; convert to boolean
        writer.writeBoolean(!this._blacklist.get(account).isZero());
        return writer;
    }

    @method(
        { name: 'account', type: ABIDataTypes.ADDRESS },
        { name: 'role', type: ABIDataTypes.UINT256 },
    )
    @returns({ name: 'hasRole', type: ABIDataTypes.BOOL })
    public checkHasRole(calldata: Calldata): BytesWriter {
        const account = calldata.readAddress();
        const role = calldata.readU256();

        const writer = new BytesWriter(1);
        writer.writeBoolean(this.hasRole(account, role));
        return writer;
    }

    @method({ name: 'minter', type: ABIDataTypes.ADDRESS })
    @returns({ name: 'allowance', type: ABIDataTypes.UINT256 })
    public minterAllowance(calldata: Calldata): BytesWriter {
        const minter = calldata.readAddress();

        const writer = new BytesWriter(32);
        writer.writeU256(this._minterAllowance.get(minter));
        return writer;
    }

    @method()
    @returns({ name: 'masterMinter', type: ABIDataTypes.ADDRESS })
    public getMasterMinter(_calldata: Calldata): BytesWriter {
        const writer = new BytesWriter(32);
        writer.writeAddress(this._masterMinter.value);
        return writer;
    }
}
```

## Key Patterns Summary

### Bitwise Operations on u256

Use `SafeMath` methods for bitwise operations:

```typescript
// Grant role (OR)
const newRoles = SafeMath.or(currentRoles, role);

// Revoke role (AND with inverted mask)
const invertedRole = SafeMath.xor(role, u256.Max);
const newRoles = SafeMath.and(currentRoles, invertedRole);

// Check role (AND)
const hasRole = !SafeMath.and(roles, role).isZero();
```

## Solidity Equivalent

For developers familiar with Solidity, here is the equivalent implementation using OpenZeppelin's ERC20Pausable and AccessControl:

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

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";

contract Stablecoin is ERC20, ERC20Pausable, AccessControl {
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
    bytes32 public constant BLACKLISTER_ROLE = keccak256("BLACKLISTER_ROLE");

    address public masterMinter;
    mapping(address => bool) public blacklisted;
    mapping(address => uint256) public minterAllowance;

    event Blacklisted(address indexed account);
    event UnBlacklisted(address indexed account);
    event MinterConfigured(address indexed minter, uint256 allowance);
    event MinterRemoved(address indexed minter);

    modifier notBlacklisted(address account) {
        require(!blacklisted[account], "Blacklisted");
        _;
    }

    constructor(
        string memory name,
        string memory symbol,
        address admin,
        address _masterMinter
    ) ERC20(name, symbol) {
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(PAUSER_ROLE, admin);
        _grantRole(BLACKLISTER_ROLE, admin);
        masterMinter = _masterMinter;
    }

    function decimals() public pure override returns (uint8) {
        return 6; // USDC-style
    }

    // ============ MINTING ============

    function configureMinter(address minter, uint256 allowance) external {
        require(msg.sender == masterMinter, "Caller is not master minter");
        if (!hasRole(MINTER_ROLE, minter)) {
            _grantRole(MINTER_ROLE, minter);
        }
        minterAllowance[minter] = allowance;
        emit MinterConfigured(minter, allowance);
    }

    function removeMinter(address minter) external {
        require(msg.sender == masterMinter, "Caller is not master minter");
        _revokeRole(MINTER_ROLE, minter);
        minterAllowance[minter] = 0;
        emit MinterRemoved(minter);
    }

    function mint(address to, uint256 amount)
        external
        onlyRole(MINTER_ROLE)
        whenNotPaused
        notBlacklisted(msg.sender)
        notBlacklisted(to)
    {
        require(minterAllowance[msg.sender] >= amount, "Minter allowance exceeded");
        minterAllowance[msg.sender] -= amount;
        _mint(to, amount);
    }

    function burn(uint256 amount)
        external
        onlyRole(MINTER_ROLE)
        whenNotPaused
        notBlacklisted(msg.sender)
    {
        _burn(msg.sender, amount);
    }

    // ============ PAUSABLE ============

    function pause() external onlyRole(PAUSER_ROLE) {
        _pause();
    }

    function unpause() external onlyRole(PAUSER_ROLE) {
        _unpause();
    }

    // ============ BLACKLIST ============

    function blacklist(address account) external onlyRole(BLACKLISTER_ROLE) {
        blacklisted[account] = true;
        emit Blacklisted(account);
    }

    function unBlacklist(address account) external onlyRole(BLACKLISTER_ROLE) {
        blacklisted[account] = false;
        emit UnBlacklisted(account);
    }

    // ============ TRANSFER OVERRIDES ============

    function _update(address from, address to, uint256 value)
        internal
        override(ERC20, ERC20Pausable)
        notBlacklisted(from)
        notBlacklisted(to)
    {
        super._update(from, to, value);
    }
}
```

## Solidity vs OP_NET Comparison

### Key Differences Table

| Aspect | Solidity (OpenZeppelin) | OP_NET |
|--------|------------------------|-------|
| **Access Control** | `AccessControl` with `bytes32` role hashes | Bit flags in `u256` with enum |
| **Role Definition** | `keccak256("MINTER_ROLE")` | `enum Role { MINTER = 2 }` (powers of 2) |
| **Role Check** | `hasRole(MINTER_ROLE, account)` | `!SafeMath.and(roles, role).isZero()` |
| **Role Grant** | `_grantRole(role, account)` | `SafeMath.or(currentRoles, role)` |
| **Pausable** | `ERC20Pausable` extension | Manual `_paused: StoredBoolean` |
| **Modifiers** | `whenNotPaused`, `onlyRole()` | Inline function calls |
| **Blacklist** | `mapping(address => bool)` | `AddressMemoryMap` |
| **Multiple Inheritance** | `is ERC20, ERC20Pausable, AccessControl` | Single `extends OP20` |
| **Decimals** | Override `decimals()` function | Set in `OP20InitParameters` |

### Role System Comparison

**Solidity (OpenZeppelin AccessControl):**
```solidity
// Roles as keccak256 hashes
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");

// Each role is a separate mapping
mapping(bytes32 role => mapping(address account => bool)) private _roles;

// Check role
function hasRole(bytes32 role, address account) public view returns (bool) {
    return _roles[role][account];
}

// Grant role
function grantRole(bytes32 role, address account) public onlyRole(getRoleAdmin(role)) {
    _grantRole(role, account);
}
```

**OP_NET (Bit Flag System):**
```typescript
// Roles as bit flags (powers of 2)
enum Role {
    ADMIN = 1,       // 0001
    MINTER = 2,      // 0010
    PAUSER = 4,      // 0100
    BLACKLISTER = 8  // 1000
}

// All roles stored in single u256 per address
private _roles: AddressMemoryMap;  // address -> u256 (combined roles)

// Check role using bitwise AND
public hasRole(account: Address, role: u256): bool {
    const roles = this._roles.get(account);
    return !SafeMath.and(roles, role).isZero();
}

// Grant role using bitwise OR
private _grantRole(account: Address, role: u256): void {
    const currentRoles = this._roles.get(account);
    const newRoles = SafeMath.or(currentRoles, role);
    this._roles.set(account, newRoles);
}
```

### Pausable Pattern Comparison

**Solidity (OpenZeppelin ERC20Pausable):**
```solidity
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";

contract MyToken is ERC20Pausable {
    function pause() external onlyRole(PAUSER_ROLE) {
        _pause();  // Built-in from Pausable
    }

    function unpause() external onlyRole(PAUSER_ROLE) {
        _unpause();  // Built-in from Pausable
    }

    // Transfers automatically checked via _update override
}
```

**OP_NET (Manual Implementation):**
```typescript
private _paused: StoredBoolean;

private whenNotPaused(): void {
    if (this._paused.value) {
        throw new Revert('Pausable: paused');
    }
}

@method()
@emit('Paused')
public pause(_calldata: Calldata): BytesWriter {
    this.onlyRole(u256.fromU64(Role.PAUSER));
    this._paused.value = true;
    this.emitEvent(new Paused(Blockchain.tx.sender));
    return new BytesWriter(0);
}

// Must manually call whenNotPaused() in each method
public override transfer(calldata: Calldata): BytesWriter {
    this.whenNotPaused();
    // ... rest of transfer logic
}
```

### Blacklist Pattern Comparison

**Solidity:**
```solidity
mapping(address => bool) public blacklisted;

modifier notBlacklisted(address account) {
    require(!blacklisted[account], "Blacklisted");
    _;
}

function blacklist(address account) external onlyRole(BLACKLISTER_ROLE) {
    blacklisted[account] = true;
    emit Blacklisted(account);
}
```

**OP_NET:**
```typescript
private _blacklist: AddressMemoryMap;

private notBlacklisted(account: Address): void {
    // AddressMemoryMap.get() returns u256; non-zero means blacklisted
    if (!this._blacklist.get(account).isZero()) {
        throw new Revert('Blacklisted');
    }
}

@method({ name: 'account', type: ABIDataTypes.ADDRESS })
@emit('Blacklisted')
public blacklist(calldata: Calldata): BytesWriter {
    this.onlyRole(u256.fromU64(Role.BLACKLISTER));
    const account = calldata.readAddress();
    // AddressMemoryMap stores u256; use u256.One for true
    this._blacklist.set(account, u256.One);
    this.emitEvent(new Blacklisted(account));
    return new BytesWriter(0);
}
```

### Advantages of OP_NET Approach

| Feature | Benefit |
|---------|---------|
| **Efficient Role Storage** | Single u256 per address stores all roles efficiently |
| **No Role Admin Complexity** | Simpler role hierarchy without OpenZeppelin's role admin system |
| **Explicit Control Flow** | Manual checks make security-critical code paths visible |
| **Bitcoin Security** | Inherits Bitcoin's proven consensus and security model |
| **No Diamond Problem** | Single inheritance avoids Solidity's multiple inheritance issues |
| **Custom Minter Allowance** | Built-in per-minter supply caps (like USDC) |

### Minter Allowance Pattern (USDC-style)

Both implementations support minter allowances, but OP_NET makes this a first-class feature:

**Solidity:**
```solidity
mapping(address => uint256) public minterAllowance;

function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) {
    require(minterAllowance[msg.sender] >= amount, "Minter allowance exceeded");
    minterAllowance[msg.sender] -= amount;
    _mint(to, amount);
}
```

**OP_NET:**
```typescript
private _minterAllowance: AddressMemoryMap;

@method(...)
public mint(calldata: Calldata): BytesWriter {
    this.onlyRole(u256.fromU64(Role.MINTER));
    // ... validation
    const allowance = this._minterAllowance.get(minter);
    if (allowance < amount) {
        throw new Revert('Minter allowance exceeded');
    }
    this._minterAllowance.set(minter, SafeMath.sub(allowance, amount));
    this._mint(to, amount);
    return new BytesWriter(0);
}
```

---

**Navigation:**
- Previous: [NFT with Reservations](./nft-with-reservations.md)
- Next: [Oracle Integration](./oracle-integration.md)
