# Vaultfire Live NFT Contracts — Technical Reference

> **Codex Batch 1 · Base Mainnet (chain 8453)**
> Compiler: solc 0.8.25 · Optimizer: 200 runs · via-ir · EVM target: cancun
> Deployer/owner: ghostkey.base.eth = `0xfA15Ee28939B222B0448261A22156070f0A7813C`
> Deploy factory: Arachnid CREATE2 `0x4e59b44847b379578588920ca78fbf26c0b4956c`

---

## Table of Contents

1. [VaultfireGenesisNFT](#1-vaultfiregenesisnft)
   - 1.1 [Header](#11-header)
   - 1.2 [Full Source Code](#12-full-source-code)
   - 1.3 [Line-by-Line Walkthrough](#13-line-by-line-walkthrough)
   - 1.4 [State Variables](#14-state-variables)
   - 1.5 [Functions](#15-functions)
   - 1.6 [Math](#16-math)
   - 1.7 [Events](#17-events)
   - 1.8 [Security Invariants](#18-security-invariants)
2. [VaultfireFoundersEdition](#2-vaultfirefoundersedition)
   - 2.1 [Header](#21-header)
   - 2.2 [Full Source Code](#22-full-source-code)
   - 2.3 [Line-by-Line Walkthrough](#23-line-by-line-walkthrough)
   - 2.4 [State Variables](#24-state-variables)
   - 2.5 [Functions](#25-functions)
   - 2.6 [Math](#26-math)
   - 2.7 [Events](#27-events)
   - 2.8 [Security Invariants](#28-security-invariants)
3. [Deploy.s.sol](#3-deployssol)
   - 3.1 [Header](#31-header)
   - 3.2 [Full Source Code](#32-full-source-code)
   - 3.3 [Line-by-Line Walkthrough](#33-line-by-line-walkthrough)
4. [Rebuild Recipe](#4-rebuild-recipe)

---

# 1. VaultfireGenesisNFT

## 1.1 Header

**File:** `contracts/VaultfireGenesisNFT.sol`
**On-chain address:** `0xdC5fE0da17864608eDC3c24A675E6C4C24a9832C` (Base mainnet, chain 8453)
**CREATE2 salt:** `0x7661756c74666972652d67656e657369732d3230323600000000000000000000` (ASCII: `vaultfire-genesis-2026`)

VaultfireGenesisNFT is a hand-rolled, dependency-free ERC-721 smart contract representing a single, unrepeatable commemorative token for the Vaultfire protocol's genesis event. It mints exactly one token — token ID `1` — directly to the designated recipient at construction time, with no possibility of further minting by anyone. The token's metadata URL is set at deploy time and points to a canonical JSON file at `theloopbreaker.com`. The owner retains the ability to update that URL once (an escape hatch in case the host changes), but has no other privileged capabilities: ownership of the contract cannot be transferred, no new tokens can ever be minted, and the token itself can be freely transferred on the secondary market via standard ERC-721 mechanics. The contract is self-contained at 137 lines — no OpenZeppelin, no imports, no dependencies whatsoever.

---

## 1.2 Full Source Code

```solidity
     1	// SPDX-License-Identifier: MIT
     2	pragma solidity 0.8.25;
     3	
     4	/// @title  Vaultfire Genesis NFT
     5	/// @notice One-of-one commemorative NFT for the Vaultfire protocol genesis.
     6	/// @dev    Minimal ERC-721 with a single immutable token (#1) minted to the
     7	///         deployer at construction. The tokenURI is fixed at deploy time
     8	///         and points to canonical JSON metadata served from
     9	///         theloopbreaker.com/nft/vaultfire-genesis.json.
    10	contract VaultfireGenesisNFT {
    11	    string  public constant name   = "Vaultfire Genesis";
    12	    string  public constant symbol = "VFGEN";
    13	
    14	    address public immutable owner;
    15	    string  private _tokenURI;
    16	    bool    public minted;
    17	
    18	    mapping(uint256 => address) private _ownerOf;
    19	    mapping(address => uint256) private _balanceOf;
    20	    mapping(uint256 => address) private _approved;
    21	    mapping(address => mapping(address => bool)) private _operatorApprovals;
    22	
    23	    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
    24	    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
    25	    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
    26	
    27	    error AlreadyMinted();
    28	    error NotOwner();
    29	    error NotMinted();
    30	    error ZeroAddress();
    31	    error NotAuthorized();
    32	    error WrongFrom();
    33	    error InvalidReceiver();
    34	
    35	    /// @param recipient_ The address that receives ownership of the contract
    36	    ///                   AND token #1. Required because the contract is deployed
    36	    ///                   via the Arachnid CREATE2 factory, so msg.sender is the
    37	    ///                   factory — not the user. Must be passed explicitly.
    38	    /// @param tokenURI_  The canonical metadata URL (e.g. https://theloopbreaker.com/nft/vaultfire-genesis.json).
    39	    constructor(address recipient_, string memory tokenURI_) {
    40	        if (recipient_ == address(0)) revert ZeroAddress();
    41	        owner = recipient_;
    42	        _tokenURI = tokenURI_;
    43	        // Auto-mint #1 to recipient on construction
    44	        _ownerOf[1] = recipient_;
    45	        _balanceOf[recipient_] = 1;
    46	        minted = true;
    47	        emit Transfer(address(0), recipient_, 1);
    48	    }
    49	
    50	    /// @notice Allow the owner to update the metadata URL once if the host changes.
    51	    /// @dev    One-shot escape hatch; can be hard-disabled by passing the zero string.
    52	    function setTokenURI(string calldata newURI) external {
    53	        if (msg.sender != owner) revert NotOwner();
    54	        _tokenURI = newURI;
    55	    }
    56	
    57	    function tokenURI(uint256 tokenId) external view returns (string memory) {
    58	        if (tokenId != 1 || !minted) revert NotMinted();
    59	        return _tokenURI;
    58	    }
    59	
    60	    /* ───── ERC-721 minimal surface ───── */
    61	
    62	    function balanceOf(address who) external view returns (uint256) {
    63	        if (who == address(0)) revert ZeroAddress();
    64	        return _balanceOf[who];
    65	    }
    66	
    67	    function ownerOf(uint256 tokenId) public view returns (address) {
    68	        address o = _ownerOf[tokenId];
    69	        if (o == address(0)) revert NotMinted();
    70	        return o;
    71	    }
    72	
    73	    function approve(address to, uint256 tokenId) external {
    74	        address o = ownerOf(tokenId);
    75	        if (msg.sender != o && !_operatorApprovals[o][msg.sender]) revert NotAuthorized();
    76	        _approved[tokenId] = to;
    77	        emit Approval(o, to, tokenId);
    78	    }
    79	
    80	    function getApproved(uint256 tokenId) external view returns (address) {
    81	        ownerOf(tokenId); // reverts if not minted
    82	        return _approved[tokenId];
    83	    }
    84	
    85	    function setApprovalForAll(address operator, bool approved) external {
    86	        _operatorApprovals[msg.sender][operator] = approved;
    87	        emit ApprovalForAll(msg.sender, operator, approved);
    88	    }
    89	
    90	    function isApprovedForAll(address o, address operator) external view returns (bool) {
    91	        return _operatorApprovals[o][operator];
    92	    }
    93	
    94	    function transferFrom(address from, address to, uint256 tokenId) public {
    95	        address o = ownerOf(tokenId);
    96	        if (o != from) revert WrongFrom();
    97	        if (to == address(0)) revert InvalidReceiver();
    98	        if (
    99	            msg.sender != o &&
   100	            _approved[tokenId] != msg.sender &&
   101	            !_operatorApprovals[o][msg.sender]
   102	        ) revert NotAuthorized();
    103	        delete _approved[tokenId];
    104	        _balanceOf[from] -= 1;
    105	        _balanceOf[to]   += 1;
    106	        _ownerOf[tokenId] = to;
    107	        emit Transfer(from, to, tokenId);
    108	    }
    109	
    110	    function safeTransferFrom(address from, address to, uint256 tokenId) external {
    111	        transferFrom(from, to, tokenId);
    112	    }
    113	
    114	    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata) external {
    115	        transferFrom(from, to, tokenId);
    116	    }
    117	
    118	    function totalSupply() external pure returns (uint256) { return 1; }
    119	    function tokenByIndex(uint256 i) external pure returns (uint256) { require(i == 0, "out"); return 1; }
    120	    function tokenOfOwnerByIndex(address o, uint256 i) external view returns (uint256) {
    121	        require(i == 0 && _balanceOf[o] == 1, "out");
    122	        require(_ownerOf[1] == o, "not owner");
    123	        return 1;
    124	    }
    125	
    126	    /// @notice ERC-165
    127	    function supportsInterface(bytes4 iid) external pure returns (bool) {
    128	        return
    129	            iid == 0x01ffc9a7 || // ERC-165
    130	            iid == 0x80ac58cd || // ERC-721
    131	            iid == 0x5b5e139f;   // ERC-721 Metadata
    132	    }
    133	}
```

---

## 1.3 Line-by-Line Walkthrough

**Line 1 — `// SPDX-License-Identifier: MIT`**
SPDX license header. Signals that the source is released under the MIT license. Etherscan and other block explorers use this tag for source verification display. It is a comment only; it has no on-chain effect.

**Line 2 — `pragma solidity 0.8.25;`**
Pins the compiler to an exact version — `0.8.25`. Using an exact version (no `^` caret) means the contract will not compile under any other compiler release. This is a deliberate choice: it guarantees deterministic bytecode and prevents silent behaviour changes introduced by future compiler updates. 0.8.25 is the version used for the live deployment.

**Lines 4–9 — NatSpec doc comment block**
Describes the contract at the file level using Ethereum's NatSpec convention (`@title`, `@notice`, `@dev`). These are consumed by documentation generators and surfaced in tools like Etherscan's "Read Contract" interface. No on-chain effect.

**Line 10 — `contract VaultfireGenesisNFT {`**
Declares the contract. There is no inheritance (`is` keyword absent). This is intentional — the entire ERC-721 implementation is written inline with no external dependencies or imported base classes.

**Line 11 — `string public constant name = "Vaultfire Genesis";`**
Declares the ERC-721 required `name` string. `public` generates a free getter function. `constant` means the value is embedded directly into the contract bytecode at compile time; it occupies no storage slot and costs only the gas of reading it from the bytecode. The value `"Vaultfire Genesis"` appears in marketplace listings and wallet interfaces.

**Line 12 — `string public constant symbol = "VFGEN";`**
The ERC-721 required ticker symbol. Same semantics as `name` — `constant`, no storage slot. `"VFGEN"` is the four-letter abbreviation shown in OpenSea, Blur, and other indexers.

**Line 14 — `address public immutable owner;`**
Declares the contract owner. `immutable` means it is written once in the constructor and then frozen into the contract's code section (like a constant, but set at deploy time rather than compile time). It occupies no persistent storage slot after construction. `public` generates a getter. Critically, there is no `transferOwnership` function — once set, this value can never change.

**Line 15 — `string private _tokenURI;`**
Holds the metadata URL. `private` — not directly readable by other contracts via a generated getter, though it is still readable by anyone who knows the slot layout (all EVM storage is public data). The leading underscore signals a private/internal variable by Solidity convention. This is the one mutable piece of state that the owner can update via `setTokenURI`.

**Line 16 — `bool public minted;`**
A boolean flag that is set to `true` in the constructor immediately after token #1 is assigned. Its purpose is to confirm that the single token exists. The `tokenURI` function checks this flag as a secondary guard against returning metadata for a hypothetically non-existent token. In practice, because there is no `burn` function and the constructor always sets this to `true`, it will always be `true` for the lifetime of the contract.

**Line 18 — `mapping(uint256 => address) private _ownerOf;`**
The canonical token ownership table. Maps token ID → current owner address. Only token ID `1` will ever have a non-zero entry. A zero value means the token either does not exist or has been burned (not possible here). This mapping occupies Keccak-derived storage slots; it does not pack into a single slot.

**Line 19 — `mapping(address => uint256) private _balanceOf;`**
Maps each address to the number of tokens it holds. The value for any given address is always either 0 or 1, because there is only one token. Updated on every transfer.

**Line 20 — `mapping(uint256 => address) private _approved;`**
Per-token approval map. Maps token ID → the single address approved to move that specific token. Cleared (deleted) on every transfer, per ERC-721 spec.

**Line 21 — `mapping(address => mapping(address => bool)) private _operatorApprovals;`**
Operator approval table. Maps owner address → operator address → `true/false`. An `approved` operator can transfer any token owned by the granting address. Used by NFT marketplaces (e.g. OpenSea's Seaport) to move tokens on behalf of sellers.

**Lines 23–25 — Event declarations**
Three events mandated by ERC-721:
- `Transfer(from, to, tokenId)` — fired on every token movement, including the mint (where `from` is `address(0)`).
- `Approval(owner, approved, tokenId)` — fired when `approve` is called.
- `ApprovalForAll(owner, operator, approved)` — fired when `setApprovalForAll` is called.
All three parameters are `indexed`, allowing efficient off-chain filtering by any of the three values.

**Lines 27–33 — Custom error declarations**
Solidity custom errors (introduced in 0.8.4). These replace `require(condition, "string")` with a more gas-efficient pattern. When a revert fires, the ABI-encoded error selector is returned instead of a full string, saving calldata cost. Each error is named descriptively:
- `AlreadyMinted()` — would guard a second mint, though it is never reached in practice (no public mint function exists).
- `NotOwner()` — caller is not `owner`.
- `NotMinted()` — queried token does not exist.
- `ZeroAddress()` — address(0) passed where a real address is required.
- `NotAuthorized()` — caller lacks approval to operate on the token.
- `WrongFrom()` — the `from` address in a transfer does not match the actual owner.
- `InvalidReceiver()` — transfer destination is `address(0)`.

**Lines 35–48 — Constructor**
The constructor accepts two arguments:
1. `recipient_` — the address that will own the contract and hold token #1.
2. `tokenURI_` — the metadata URL string.

- **Line 40** — Guards against the zero address. If someone accidentally passes `address(0)` (e.g. omits the argument), the transaction reverts with `ZeroAddress()`. Critically important because `owner` is `immutable` — a wrong address would be permanent.
- **Line 42** — `owner = recipient_` — writes `recipient_` into the `immutable` owner slot. This is the only time `owner` is ever written.
- **Line 43** — `_tokenURI = tokenURI_` — stores the metadata URL into mutable private storage.
- **Line 45** — `_ownerOf[1] = recipient_` — assigns ownership of token #1 to the recipient. This is the mint. No `totalSupply` counter is needed beyond the constant `1`.
- **Line 46** — `_balanceOf[recipient_] = 1` — records that the recipient now holds one token.
- **Line 47** — `minted = true` — flips the existence flag.
- **Line 48** — `emit Transfer(address(0), recipient_, 1)` — broadcasts the mint event. Indexers (OpenSea, etc.) watch for `Transfer` events with `from == address(0)` to detect mints.

**Lines 52–55 — `setTokenURI`**
The owner's only privileged function. Takes a new URL string and overwrites `_tokenURI`. Protected by `if (msg.sender != owner) revert NotOwner()`. There is no re-entrancy concern here as no ETH is moved and no external calls are made. The NatSpec comment mentions it can be "hard-disabled" by passing the empty string `""` — doing so would make `tokenURI` return an empty string, effectively breaking metadata display, so this is a nuclear option for the owner.

**Lines 57–61 — `tokenURI`**
Returns the metadata URL for a given token ID. Reverts with `NotMinted()` if the caller queries any ID other than `1` or if (hypothetically) `minted` were false. Because there is only one token and `minted` is always `true`, the only live revert path here is querying a wrong ID (e.g. `tokenURI(2)`).

**Lines 65–68 — `balanceOf`**
Returns how many tokens an address holds. Guards against `address(0)` per ERC-721 spec requirement. Delegates to the `_balanceOf` mapping.

**Lines 70–74 — `ownerOf`**
Returns the current owner of a given token. If the mapping returns `address(0)` (meaning the token does not exist), reverts `NotMinted()`. This is `public` (not `external`) so it can be called from within the contract itself — specifically from `approve` and `transferFrom`, which need the owner address internally.

**Lines 76–81 — `approve`**
Grants a specific address permission to transfer a specific token. Requires the caller to be either the token's owner or an approved operator for that owner. Writes the approved address to `_approved[tokenId]`. Emits `Approval`. Note: the approve is cleared whenever the token is transferred.

**Lines 83–86 — `getApproved`**
Returns the currently approved address for a token. Calls `ownerOf(tokenId)` internally — this serves as a "does this token exist" check that reverts if not. Then reads `_approved[tokenId]`, which returns `address(0)` if no approval is set.

**Lines 88–91 — `setApprovalForAll`**
Grants or revokes operator-level approval. An operator can transfer any token owned by the caller. This is used by marketplace contracts (they call `setApprovalForAll` once per user, then the user can list/sell any of their tokens without further approvals). No zero-address check on `operator` — consistent with ERC-721 spec which does not require it.

**Lines 93–95 — `isApprovedForAll`**
Pure view into the `_operatorApprovals` nested mapping. No validation; returns `false` for any pair that has never been set.

**Lines 97–111 — `transferFrom`**
The core transfer function. Steps:
1. Fetch current owner via `ownerOf(tokenId)` — reverts if token does not exist.
2. Assert `o == from` — the stated `from` must match the actual owner. Reverts `WrongFrom()` if not.
3. Assert `to != address(0)` — prevents burning via this path.
4. Authorization check: caller must be the owner, OR the approved address for this token, OR an approved operator for the owner. Any other caller reverts `NotAuthorized()`.
5. `delete _approved[tokenId]` — clears the per-token approval per ERC-721 spec (prevents stale approvals being exploited after a sale).
6. Decrement `_balanceOf[from]` and increment `_balanceOf[to]`.
7. Update `_ownerOf[tokenId]` to `to`.
8. Emit `Transfer`.
Note: `public` visibility (not `external`) so the `safeTransferFrom` overloads can call it internally.

**Lines 113–116 — `safeTransferFrom(from, to, tokenId)`**
The no-data-payload overload of safe transfer. Delegates to `transferFrom`. In this contract, there is no `onERC721Received` callback check — the "safe" here is nominal for Genesis (the token's uniqueness means the risk profile is different from bulk NFT collections). Platforms call this variant as the default path.

**Lines 117–119 — `safeTransferFrom(from, to, tokenId, bytes)`**
Same as above but accepts arbitrary `bytes` data. The data parameter is silently discarded (no named parameter). Also delegates to plain `transferFrom`.

**Line 121 — `totalSupply()`**
Returns the hardcoded constant `1`. `pure` means it reads no state. This satisfies the ERC-721 Enumerable interface expectation and lets marketplaces know there is exactly one token.

**Line 122 — `tokenByIndex(uint256 i)`**
ERC-721 Enumerable compatibility. Only `i == 0` is valid (index into a one-element array), returning `1`. Reverts with a legacy `require` string `"out"` for any other index.

**Lines 123–127 — `tokenOfOwnerByIndex`**
Returns which token a given owner holds at a given index. Because there is only one token, only `i == 0` is valid, and only the actual holder of token #1 gets a non-revert result. Uses `require` with strings rather than custom errors — a minor inconsistency with the rest of the file, likely because these enumerable helpers were added as an afterthought.

**Lines 129–135 — `supportsInterface`**
ERC-165 introspection. Returns `true` for three interface IDs:
- `0x01ffc9a7` — ERC-165 itself
- `0x80ac58cd` — ERC-721
- `0x5b5e139f` — ERC-721 Metadata (tokenURI extension)

This tells marketplaces and wallets that the contract speaks these standards. Note: ERC-721 Enumerable (`0x780e9d63`) is **not** declared here despite the enumerable helper functions being present — a cosmetic inconsistency, but enumerable helpers still work if called directly.

---

## 1.4 State Variables

| Variable | Type | Storage Slot Behavior | Visibility | Mutable | Who Can Change |
|---|---|---|---|---|---|
| `name` | `string` | No slot — compile-time constant in bytecode | `public` | No | Nobody |
| `symbol` | `string` | No slot — compile-time constant in bytecode | `public` | No | Nobody |
| `owner` | `address` | Immutable — stored in code section after construction | `public` | No | Nobody (no setter) |
| `_tokenURI` | `string` | Slot 0 (and overflow slots for long strings) | `private` | Yes | `owner` via `setTokenURI` |
| `minted` | `bool` | Slot 1 (packed with other booleans if any, but here it is alone) | `public` | No after constructor | Nobody (no setter; set to true in constructor) |
| `_ownerOf` | `mapping(uint256 => address)` | Dynamic — each key hashes to `keccak256(key ‖ slot 2)` | `private` | Yes | `transferFrom` (internal) |
| `_balanceOf` | `mapping(address => uint256)` | Dynamic — each key hashes to `keccak256(key ‖ slot 3)` | `private` | Yes | Constructor, `transferFrom` |
| `_approved` | `mapping(uint256 => address)` | Dynamic — each key hashes to `keccak256(key ‖ slot 4)` | `private` | Yes | `approve`, `transferFrom` (deleted) |
| `_operatorApprovals` | `mapping(address => mapping(address => bool))` | Dynamic nested — `keccak256(operator ‖ keccak256(owner ‖ slot 5))` | `private` | Yes | `setApprovalForAll` |

**Slot layout note:** Solidity assigns storage slots sequentially to state variables in declaration order (post-constants and immutables, which don't use slots). `_tokenURI` (string) occupies slot 0 (length-encoding and pointer), `minted` occupies slot 1, and the four mappings occupy slots 2–5 as base positions for their dynamic slot computations.

---

## 1.5 Functions

### `constructor(address recipient_, string memory tokenURI_)`

| Attribute | Value |
|---|---|
| Visibility | N/A (constructor) |
| Modifiers | None |
| Payable | No |
| State changes | Sets `owner`, `_tokenURI`, `_ownerOf[1]`, `_balanceOf[recipient_]`, `minted` |
| Who calls it | Deploy script / CREATE2 factory (once only, at deploy time) |
| Returns | Nothing |
| Reverts | `ZeroAddress()` if `recipient_` is `address(0)` |
| Emits | `Transfer(address(0), recipient_, 1)` |

**Description:** Runs exactly once at deployment. Writes all initial state and mints token #1 to `recipient_`. Because deployment is via the Arachnid CREATE2 factory, `msg.sender` is the factory contract — not the user — so the intended owner must be passed explicitly as `recipient_`.

---

### `setTokenURI(string calldata newURI)`

| Attribute | Value |
|---|---|
| Visibility | `external` |
| Modifiers | Manual `msg.sender == owner` check |
| Payable | No |
| State changes | Overwrites `_tokenURI` |
| Who calls it | `owner` only |
| Returns | Nothing |
| Reverts | `NotOwner()` if caller is not `owner` |
| Emits | Nothing |

**Description:** Allows the contract owner to point the metadata URL at a new location. Useful if the hosting domain changes. Can be set to `""` (empty string) to effectively nullify the metadata display. There is no event emitted on URI change — off-chain indexers relying on events would not automatically pick up the new URL.

---

### `tokenURI(uint256 tokenId) → string`

| Attribute | Value |
|---|---|
| Visibility | `external view` |
| Modifiers | None |
| Payable | No |
| State changes | None |
| Who calls it | Anyone |
| Returns | The metadata URL string |
| Reverts | `NotMinted()` if `tokenId != 1` or `minted == false` |
| Emits | Nothing |

**Description:** Returns the canonical metadata JSON URL for the single token. The URL at deploy time is `https://theloopbreaker.com/nft/vaultfire-genesis.json`.

---

### `balanceOf(address who) → uint256`

| Attribute | Value |
|---|---|
| Visibility | `external view` |
| Reverts | `ZeroAddress()` if `who == address(0)` |

**Description:** Returns 0 or 1 — the number of tokens held by `who`. The ERC-721 spec requires a revert for the zero address.

---

### `ownerOf(uint256 tokenId) → address`

| Attribute | Value |
|---|---|
| Visibility | `public view` |
| Reverts | `NotMinted()` if `_ownerOf[tokenId] == address(0)` |

**Description:** Returns the current holder of a token. `public` so it is callable both externally and from within the contract. For Genesis, only `ownerOf(1)` will ever return a non-revert result.

---

### `approve(address to, uint256 tokenId)`

| Attribute | Value |
|---|---|
| Visibility | `external` |
| Reverts | `NotMinted()` (via `ownerOf`), `NotAuthorized()` |
| Emits | `Approval(owner, to, tokenId)` |

**Description:** Grants `to` the right to transfer token `tokenId`. Caller must be the token owner or an approved operator.

---

### `getApproved(uint256 tokenId) → address`

| Attribute | Value |
|---|---|
| Visibility | `external view` |
| Reverts | `NotMinted()` if token does not exist |

**Description:** Returns the currently approved address for `tokenId`, or `address(0)` if none.

---

### `setApprovalForAll(address operator, bool approved)`

| Attribute | Value |
|---|---|
| Visibility | `external` |
| Emits | `ApprovalForAll(msg.sender, operator, approved)` |

**Description:** Grants or revokes blanket operator approval over all tokens owned by `msg.sender`.

---

### `isApprovedForAll(address o, address operator) → bool`

| Attribute | Value |
|---|---|
| Visibility | `external view` |

**Description:** Returns the current operator approval status.

---

### `transferFrom(address from, address to, uint256 tokenId)`

| Attribute | Value |
|---|---|
| Visibility | `public` |
| Reverts | `NotMinted()`, `WrongFrom()`, `InvalidReceiver()`, `NotAuthorized()` |
| Emits | `Transfer(from, to, tokenId)` |
| State changes | `_ownerOf[tokenId]`, `_balanceOf[from]`, `_balanceOf[to]`, deletes `_approved[tokenId]` |

**Description:** Core transfer. Validates ownership, authorization, and non-zero destination. Clears the per-token approval before emitting.

---

### `safeTransferFrom(address from, address to, uint256 tokenId)`

| Attribute | Value |
|---|---|
| Visibility | `external` |

**Description:** Thin wrapper over `transferFrom`. Does not call `onERC721Received` — the "safe" behavior is not implemented here. Satisfies the ABI signature required by ERC-721.

---

### `safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata)`

| Attribute | Value |
|---|---|
| Visibility | `external` |

**Description:** Same as above. The `bytes` data parameter is accepted but silently ignored. Required by ERC-721 spec to overload `safeTransferFrom`.

---

### `totalSupply() → uint256`

| Attribute | Value |
|---|---|
| Visibility | `external pure` |
| Returns | Always `1` |

---

### `tokenByIndex(uint256 i) → uint256`

| Attribute | Value |
|---|---|
| Visibility | `external pure` |
| Reverts | `require(i == 0, "out")` |
| Returns | Always `1` |

---

### `tokenOfOwnerByIndex(address o, uint256 i) → uint256`

| Attribute | Value |
|---|---|
| Visibility | `external view` |
| Reverts | `require(i == 0 && _balanceOf[o] == 1, "out")`, `require(_ownerOf[1] == o, "not owner")` |
| Returns | Always `1` |

---

### `supportsInterface(bytes4 iid) → bool`

| Attribute | Value |
|---|---|
| Visibility | `external pure` |
| Returns | `true` for ERC-165 (`0x01ffc9a7`), ERC-721 (`0x80ac58cd`), ERC-721 Metadata (`0x5b5e139f`) |

---

## 1.6 Math

This contract contains no financial arithmetic. The only numeric operations are:

**Balance tracking (lines 104–105 in transferFrom):**
```
_balanceOf[from] -= 1;
_balanceOf[to]   += 1;
```
These are plain integer decrements/increments. Overflow is not possible because:
- `_balanceOf[from]` is always exactly `1` before a transfer (since there is only one token and `from` must currently own it), so subtracting `1` gives `0` — never underflows.
- `_balanceOf[to]` is always `0` before a transfer to any new address (since only one token exists), so adding `1` gives `1` — never overflows a `uint256`.

**`tokenByIndex` index check (line 122):**
```
require(i == 0, "out");
```
Simple equality — no arithmetic.

**No ETH, no prices, no royalties.** Genesis is non-financial; there is no mint price, no royalty mechanism, and the contract holds no ETH at any point.

---

## 1.7 Events

### `Transfer(address indexed from, address indexed to, uint256 indexed tokenId)`
- **When fired:** Once in the constructor (the mint, where `from == address(0)` and `to == recipient_`), and once per `transferFrom` call.
- **What it logs:** The previous holder (`from`), new holder (`to`), and token ID (always `1`).
- **Why it matters:** Block explorers, NFT indexers, and marketplace platforms listen for this event to track ownership. A `Transfer` with `from == address(0)` signals a mint; a `Transfer` with `to == address(0)` signals a burn (not possible here).

### `Approval(address indexed owner, address indexed approved, uint256 indexed tokenId)`
- **When fired:** Every `approve` call.
- **What it logs:** The token owner, the newly approved address, and the token ID.

### `ApprovalForAll(address indexed owner, address indexed operator, bool approved)`
- **When fired:** Every `setApprovalForAll` call.
- **What it logs:** The token owner who is granting/revoking, the operator, and the boolean grant status.

---

## 1.8 Security Invariants

The following properties hold for all time, by design, and cannot be violated by any transaction:

1. **Supply is permanently 1.** There is no `mint` function and no way to increase `totalSupply` beyond the single token minted in the constructor.

2. **`minted` is permanently `true`.** The flag is set to `true` in the constructor and there is no setter for it. No burn function exists, so `minted` cannot become `false`.

3. **`owner` is permanently `ghostkey.base.eth` (`0xfA15Ee28939B222B0448261A22156070f0A7813C`).** The `immutable` keyword and the absence of any `transferOwnership` function make this irrevocable.

4. **Contract holds no ETH.** No function is `payable`. Any ETH sent directly to the contract will revert (no fallback or receive function).

5. **Token #1 cannot be burned.** `transferFrom` rejects `to == address(0)` with `InvalidReceiver()`. The two `safeTransferFrom` wrappers both call `transferFrom`, inheriting the same guard.

6. **Only the token owner or an approved party can transfer.** The three-way authorization check in `transferFrom` (owner, single-token approval, or operator) cannot be bypassed.

7. **Approvals are atomically cleared on transfer.** `delete _approved[tokenId]` runs before the state update and event emission — stale approvals cannot survive a transfer.

8. **`setTokenURI` cannot be called by anyone other than `owner`.** The `msg.sender != owner` check is the first operation in the function.

9. **No reentrancy surface.** No ETH is ever sent from this contract. No external contract calls are made. There is nothing to reenter.

---

# 2. VaultfireFoundersEdition

## 2.1 Header

**File:** `contracts/VaultfireFoundersEdition.sol`
**On-chain address:** `0x1bcD9B7Cad06D2BbC7FdedC46B32eFd1988e28cE` (Base mainnet, chain 8453)
**CREATE2 salt:** `0x7661756c74666972652d666f756e646572732d32303236000000000000000000` (ASCII: `vaultfire-founders-2026`)

VaultfireFoundersEdition is a minimal, dependency-free ERC-721 + ERC-2981 contract providing a publicly mintable, hard-capped edition of 5 tokens at 0.1 ETH each, with automatic proceeds forwarding and secondary-market royalties. Unlike the Genesis 1/1, anyone can call `mint()` and pay 0.1 ETH to receive a Founders Edition token sequentially numbered 1–5. Proceeds are not held in the contract — they are pushed to `ghostkey.base.eth` on every mint call via a low-level ETH transfer, meaning funds never sit in the contract. A 5% EIP-2981 royalty is declared on secondary sales (marketplace-enforced), routing to `ghostkey.base.eth`. The contract also implements a `withdraw()` fallback for any ETH that somehow accumulates, and `contractURI()` for OpenSea collection-level metadata. No external libraries are used.

---

## 2.2 Full Source Code

```solidity
     1	// SPDX-License-Identifier: MIT
     2	pragma solidity 0.8.25;
     3	
     4	/// @title  Vaultfire Founders Edition
     5	/// @notice Public 5-edition companion to the Genesis 1/1. Anyone can mint at
     6	///         a fixed price; supply hard-capped at 5; proceeds withdrawable by
     7	///         the owner; EIP-2981 5% secondary royalties to the owner.
     8	/// @dev    Minimal ERC-721 + ERC-2981. No external libraries.
     9	contract VaultfireFoundersEdition {
    10	    string  public constant name   = "Vaultfire Founders Edition";
    11	    string  public constant symbol = "VFFE";
    12	
    13	    address public immutable owner;
    14	    string  private _baseURI;
    15	    uint256 public constant MAX_SUPPLY = 5;
    16	    uint256 public constant PRICE = 0.1 ether;
    17	    uint96  public constant ROYALTY_BPS = 500; // 5%
    18	    uint256 public totalSupply;
    19	
    20	    mapping(uint256 => address) private _ownerOf;
    21	    mapping(address => uint256) private _balanceOf;
    22	    mapping(uint256 => address) private _approved;
    23	    mapping(address => mapping(address => bool)) private _operatorApprovals;
    24	
    25	    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
    26	    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
    27	    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
    28	    event Minted(address indexed to, uint256 indexed tokenId);
    29	    event Withdraw(address indexed to, uint256 amount);
    30	
    30	    error SoldOut();
    31	    error WrongPrice();
    32	    error NotOwner();
    33	    error ZeroAddress();
    34	    error NotApproved();
    35	    error NotTokenOwner();
    36	    error NonexistentToken();
    37	    error WithdrawFailed();
    38	
    39	    modifier onlyOwner() {
    40	        if (msg.sender != owner) revert NotOwner();
    41	        _;
    42	    }
    43	
    44	    /// @param owner_   The address that becomes the contract owner and receives
    45	    ///                 all mint proceeds + royalties. Required because the
    46	    ///                 contract is deployed via the Arachnid CREATE2 factory,
    47	    ///                 so msg.sender is the factory — not the user.
    48	    /// @param baseURI_ Base URL for tokenURI (e.g. https://theloopbreaker.com/nft/founders/).
    49	    constructor(address owner_, string memory baseURI_) {
    50	        if (owner_ == address(0)) revert ZeroAddress();
    49	        owner = owner_;
    50	        _baseURI = baseURI_;
    51	    }
    52	
    53	    // ---------- Mint ----------
    54	
    55	    function mint() external payable returns (uint256 tokenId) {
    56	        if (totalSupply >= MAX_SUPPLY) revert SoldOut();
    57	        if (msg.value != PRICE) revert WrongPrice();
    58	        unchecked { tokenId = ++totalSupply; }
    59	        _ownerOf[tokenId] = msg.sender;
    59	        unchecked { _balanceOf[msg.sender] += 1; }
    60	        emit Transfer(address(0), msg.sender, tokenId);
    61	        emit Minted(msg.sender, tokenId);
    62	        // Auto-forward proceeds to the owner so funds land in ghostkey.base.eth
    63	        // immediately on every mint — no manual withdraw needed.
    63	        (bool ok, ) = owner.call{value: msg.value}("");
    64	        if (!ok) revert WithdrawFailed();
    64	    }
    65	
    66	    function remaining() external view returns (uint256) {
    67	        return MAX_SUPPLY - totalSupply;
    68	    }
    69	
    70	    // ---------- Withdraw ----------
    70
    71	    function withdraw() external onlyOwner {
    72	        uint256 bal = address(this).balance;
    73	        (bool ok, ) = owner.call{value: bal}("");
    74	        if (!ok) revert WithdrawFailed();
    75	        emit Withdraw(owner, bal);
    76	    }
    77	
    78	    // ---------- Metadata ----------
    79
    80	    function setBaseURI(string calldata baseURI_) external onlyOwner {
    81	        _baseURI = baseURI_;
    82	    }
    83
    84	    function tokenURI(uint256 tokenId) external view returns (string memory) {
    85	        if (_ownerOf[tokenId] == address(0)) revert NonexistentToken();
    85	        return string(abi.encodePacked(_baseURI, _toString(tokenId), ".json"));
    86	    }
    87
    88	    function contractURI() external view returns (string memory) {
    89	        return string(abi.encodePacked(_baseURI, "contract.json"));
    89	    }
    90
    91	    // ---------- ERC-721 ----------
    92
    93	    function balanceOf(address a) external view returns (uint256) {
    94	        if (a == address(0)) revert ZeroAddress();
    95	        return _balanceOf[a];
    96	    }
    97
    98	    function ownerOf(uint256 tokenId) external view returns (address) {
    99	        address o = _ownerOf[tokenId];
    99	        if (o == address(0)) revert NonexistentToken();
   100	        return o;
   101	    }
   102
   103	    function approve(address to, uint256 tokenId) external {
   104	        address o = _ownerOf[tokenId];
   104	        if (o == address(0)) revert NonexistentToken();
   105	        if (msg.sender != o && !_operatorApprovals[o][msg.sender]) revert NotApproved();
   106	        _approved[tokenId] = to;
   107	        emit Approval(o, to, tokenId);
   108	    }
   109
   110	    function getApproved(uint256 tokenId) external view returns (address) {
   111	        if (_ownerOf[tokenId] == address(0)) revert NonexistentToken();
   112	        return _approved[tokenId];
   113	    }
   114
   115	    function setApprovalForAll(address operator, bool approved) external {
   116	        _operatorApprovals[msg.sender][operator] = approved;
   117	        emit ApprovalForAll(msg.sender, operator, approved);
   118	    }
   119
   120	    function isApprovedForAll(address o, address operator) external view returns (bool) {
   121	        return _operatorApprovals[o][operator];
   122	    }
   123
   124	    function transferFrom(address from, address to, uint256 tokenId) public {
   125	        _transfer(from, to, tokenId);
   126	    }
   127
   128	    function safeTransferFrom(address from, address to, uint256 tokenId) external {
   129	        _transfer(from, to, tokenId);
   130	        _checkOnERC721Received(from, to, tokenId, "");
   131	    }
   132
   133	    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external {
   134	        _transfer(from, to, tokenId);
   134	        _checkOnERC721Received(from, to, tokenId, data);
   135	    }
   136
   137	    function _transfer(address from, address to, uint256 tokenId) internal {
   138	        if (to == address(0)) revert ZeroAddress();
   139	        address o = _ownerOf[tokenId];
   140	        if (o == address(0)) revert NonexistentToken();
   141	        if (o != from) revert NotTokenOwner();
   142	        if (msg.sender != o
   143	            && _approved[tokenId] != msg.sender
   143	            && !_operatorApprovals[o][msg.sender]) revert NotApproved();
   144	        delete _approved[tokenId];
   145	        unchecked {
   146	            _balanceOf[from] -= 1;
   147	            _balanceOf[to]   += 1;
   148	        }
   149	        _ownerOf[tokenId] = to;
   150	        emit Transfer(from, to, tokenId);
   151	    }
   152
   153	    function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory data) internal {
   154	        if (to.code.length == 0) return;
   155	        try IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, data) returns (bytes4 ret) {
   156	            if (ret != IERC721Receiver.onERC721Received.selector) revert NotApproved();
   156	        } catch {
   157	            revert NotApproved();
   158	        }
   159	    }
   160
   161	    // ---------- ERC-2981 royalties ----------
   162
   163	    function royaltyInfo(uint256 /*tokenId*/, uint256 salePrice)
   163	        external view returns (address receiver, uint256 royaltyAmount)
   164	    {
   165	        receiver = owner;
   165	        royaltyAmount = (salePrice * ROYALTY_BPS) / 10000;
   166	    }
   167
   168	    // ---------- ERC-165 ----------
   169
   170	    function supportsInterface(bytes4 iid) external pure returns (bool) {
   171	        return
   171	            iid == 0x01ffc9a7 || // ERC-165
   172	            iid == 0x80ac58cd || // ERC-721
   173	            iid == 0x5b5e139f || // ERC-721 Metadata
   174	            iid == 0x2a55205a;   // ERC-2981
   175	    }
   176
   177	    // ---------- utils ----------
   178
   179	    function _toString(uint256 v) internal pure returns (string memory) {
   180	        if (v == 0) return "0";
   180	        uint256 n = v;
   181	        uint256 len;
   182	        while (n != 0) { len++; n /= 10; }
   183	        bytes memory b = new bytes(len);
   184	        while (v != 0) { len--; b[len] = bytes1(uint8(48 + v % 10)); v /= 10; }
   185	        return string(b);
   186	    }
   187	}
   188
   189	interface IERC721Receiver {
   190	    function onERC721Received(address, address, uint256, bytes calldata) external returns (bytes4);
   191	}
```

---

## 2.3 Line-by-Line Walkthrough

**Lines 1–2 — License and compiler pin**
Identical semantics to Genesis. MIT license, exact compiler version `0.8.25` required.

**Lines 4–8 — NatSpec block**
Documents the contract's purpose: a public 5-edition companion to the Genesis 1/1. Notes the existence of EIP-2981 royalties and the absence of external library dependencies.

**Line 9 — `contract VaultfireFoundersEdition {`**
Declares the contract. No inheritance. Self-contained.

**Line 10 — `string public constant name = "Vaultfire Founders Edition";`**
ERC-721 required name. Compile-time constant, no storage slot.

**Line 11 — `string public constant symbol = "VFFE";`**
ERC-721 required symbol. Compile-time constant, no storage slot.

**Line 13 — `address public immutable owner;`**
The permanent owner. Set once in the constructor to `ghostkey.base.eth`. Immutable — cannot change.

**Line 14 — `string private _baseURI;`**
Base URL for metadata. Token URI for token `n` will be `_baseURI + n.toString() + ".json"`. Set at construction and updatable by `owner` via `setBaseURI`.

**Line 15 — `uint256 public constant MAX_SUPPLY = 5;`**
The hard supply cap. Compile-time constant. No function in this contract can ever increase this value. The supply check `totalSupply >= MAX_SUPPLY` in `mint()` enforces it.

**Line 16 — `uint256 public constant PRICE = 0.1 ether;`**
The mint price. The `ether` suffix is a Solidity literal multiplier — `0.1 ether` compiles to the literal integer `100000000000000000` (100,000,000,000,000,000 wei). This value is embedded in bytecode at compile time and never changes. No function can alter the price.

**Line 17 — `uint96 public constant ROYALTY_BPS = 500;`**
Royalty in basis points. 500 basis points = 5%. Using `uint96` rather than `uint256` is a micro-optimization — EIP-2981's `royaltyInfo` returns a `uint256`, but the constant only needs to hold the value `500`, which fits in any integer type. `uint96` is common in OpenZeppelin's ERC-2981 implementation for this field.

**Line 18 — `uint256 public totalSupply;`**
Mutable counter. Starts at `0`. Incremented by `1` on each successful `mint()`. Readable by anyone via its auto-generated getter. When `totalSupply == 5`, all mints revert with `SoldOut()`.

**Lines 20–23 — ERC-721 storage mappings**
Identical in purpose to the Genesis contract's mappings. See the Genesis walkthrough (lines 18–21) for full explanation. Here they span token IDs 1–5 rather than just ID 1.

**Lines 25–29 — Event declarations**
Three standard ERC-721 events plus two Founders-specific additions:
- `Minted(address indexed to, uint256 indexed tokenId)` — a supplementary mint signal, fired alongside `Transfer`. Allows indexers to distinguish mints from transfers without checking `from == address(0)`.
- `Withdraw(address indexed to, uint256 amount)` — fired when the `withdraw()` function is called. Provides an on-chain audit log of ETH withdrawals.

**Lines 31–38 — Custom error declarations**
- `SoldOut()` — all 5 tokens already minted.
- `WrongPrice()` — `msg.value` does not equal `PRICE` exactly (both underpayment and overpayment revert).
- `NotOwner()` — caller is not `owner`.
- `ZeroAddress()` — address(0) passed where a real address is required.
- `NotApproved()` — caller lacks authorization to transfer or the safe-receive check failed.
- `NotTokenOwner()` — the `from` address in a transfer does not match the actual token owner.
- `NonexistentToken()` — queried token ID has not been minted yet (or does not exist).
- `WithdrawFailed()` — the low-level ETH call to `owner` failed (e.g. owner is a contract that reverts on receive).

**Lines 40–43 — `onlyOwner` modifier**
A named modifier defined explicitly rather than inherited. When applied to a function, it inserts an `owner` check at the point of the `_;` placeholder, which here is at the top of the function body. If `msg.sender != owner`, the function reverts with `NotOwner()` before executing any logic.

**Lines 49–54 — Constructor**
- **Line 51** — Guards against `address(0)` for `owner_`. Without this, the immutable `owner` could be set to the zero address, permanently locking all owner-only functions.
- **Line 52** — Sets the immutable `owner`.
- **Line 53** — Sets the mutable `_baseURI`.
- Unlike Genesis, no token is minted in the constructor. `totalSupply` starts at `0` and the contract waits for public `mint()` calls.

**Lines 58–70 — `mint()` — the core public function**
This is the only way to create Founders Edition tokens. Step-by-step:

- **Line 59** — `if (totalSupply >= MAX_SUPPLY) revert SoldOut();` — checked first before touching any state. If all 5 tokens are minted, the call reverts immediately. The `>=` (rather than `==`) is defensive — it prevents any conceivable edge case where `totalSupply` somehow exceeded 5.
- **Line 60** — `if (msg.value != PRICE) revert WrongPrice();` — exact-match price enforcement. Both underpayment (0 ETH, 0.05 ETH) and overpayment (0.2 ETH) revert. This is strict: no accidental refunds.
- **Line 61** — `unchecked { tokenId = ++totalSupply; }` — pre-increment: `totalSupply` is incremented first, then its new value becomes `tokenId`. So the first mint yields `tokenId = 1`, the second `tokenId = 2`, etc. `unchecked` removes the Solidity overflow guard — safe because `MAX_SUPPLY = 5` is checked above, so `totalSupply` can never reach `uint256` max.
- **Line 62** — `_ownerOf[tokenId] = msg.sender` — records ownership of the new token.
- **Line 63** — `unchecked { _balanceOf[msg.sender] += 1; }` — increments the minter's balance. `unchecked` is safe because no address can hold more than 5 tokens (MAX_SUPPLY).
- **Line 64** — `emit Transfer(address(0), msg.sender, tokenId)` — standard ERC-721 mint event.
- **Line 65** — `emit Minted(msg.sender, tokenId)` — additional mint event for easy indexing.
- **Line 68** — `(bool ok, ) = owner.call{value: msg.value}("")` — pushes the full `msg.value` (0.1 ETH = 100,000,000,000,000,000 wei) to `owner` via a low-level call. This is the auto-forward mechanism. Funds do not accumulate in the contract; they flow to `ghostkey.base.eth` atomically on every mint.
- **Line 69** — `if (!ok) revert WithdrawFailed();` — if the ETH transfer to `owner` fails (e.g. `owner` is a contract that reverts on receive), the entire `mint()` call reverts. This means a broken `owner` address would prevent all future mints — a notable operational risk, though ghostkey.base.eth is an EOA with a standard receive function, so this is not a concern in practice.

**Lines 72–74 — `remaining()`**
Simple view: `MAX_SUPPLY - totalSupply`. Returns how many tokens can still be minted. Returns `0` when sold out.

**Lines 78–83 — `withdraw()`**
Owner-only ETH sweep. Reads `address(this).balance` (the contract's current ETH balance), attempts to send it to `owner`, and emits `Withdraw`. In normal operation the contract holds 0 ETH (because `mint()` auto-forwards everything), so `bal = 0` and this is a no-op transfer. Its purpose is as a rescue mechanism for ETH sent directly to the contract via `selfdestruct` or similar edge cases.

**Lines 87–89 — `setBaseURI(string calldata baseURI_)`**
Owner-only metadata URL update. Overwrites `_baseURI` globally — affects all existing token URIs immediately since `tokenURI` is computed dynamically from `_baseURI`. No event emitted.

**Lines 91–94 — `tokenURI(uint256 tokenId) → string`**
Checks that the token exists (via `_ownerOf[tokenId] == address(0)` guard), then constructs the metadata URL dynamically: `_baseURI + tokenId.toString() + ".json"`. For token `3` with `_baseURI = "https://theloopbreaker.com/nft/founders/"`, this returns `"https://theloopbreaker.com/nft/founders/3.json"`.

**Lines 96–98 — `contractURI() → string`**
Returns a collection-level metadata URL: `_baseURI + "contract.json"`. This is an OpenSea convention (not part of ERC-721 spec) that allows collection-level metadata (name, description, image, royalties) to be hosted at a well-known URL. For `_baseURI = "https://theloopbreaker.com/nft/founders/"`, this returns `"https://theloopbreaker.com/nft/founders/contract.json"`.

**Lines 101–105 — `balanceOf` / `ownerOf`**
Identical in purpose to Genesis equivalents. `ownerOf` here is `external` (not `public`), so it cannot be called by internal functions — internal code uses `_ownerOf[tokenId]` directly instead.

**Lines 107–130 — `approve`, `getApproved`, `setApprovalForAll`, `isApprovedForAll`**
Standard ERC-721 approval mechanisms. Functionally identical to Genesis equivalents.

**Lines 132–136 — `transferFrom`, `safeTransferFrom` (both overloads)**
Unlike Genesis where these three functions each contained the full transfer logic inline, Founders Edition extracts the logic into `_transfer`. The `safeTransferFrom` variants additionally call `_checkOnERC721Received`.

**Lines 137–151 — `_transfer` (internal)**
The internal transfer implementation. Logic mirrors Genesis's `transferFrom` directly, with two differences:
1. `unchecked` block wraps the balance update (lines 144–148), saving a small amount of gas.
2. Error names differ slightly (`NotTokenOwner` vs `WrongFrom` in Genesis, `NotApproved` for authorization vs `NotAuthorized`).

**Lines 153–159 — `_checkOnERC721Received`**
This is the key difference between `safeTransferFrom` and `transferFrom`. Before calling, it checks if `to` is a contract (`to.code.length == 0` short-circuits for EOAs). If `to` is a contract, it calls `to.onERC721Received(operator, from, tokenId, data)`. If the return value is not the expected selector (`0x150b7a02`), or if the call reverts, the transfer reverts with `NotApproved()`. This prevents tokens from being locked in contracts that don't implement ERC-721 receiver logic.

**Lines 162–166 — `royaltyInfo`**
The EIP-2981 implementation. Takes a `salePrice` (in wei) and returns the royalty receiver (`owner = ghostkey.base.eth`) and the royalty amount. Calculation: `(salePrice * 500) / 10000`. The `tokenId` parameter is accepted but ignored (same royalty for all tokens). See Section 2.6 for the full arithmetic breakdown.

**Lines 169–175 — `supportsInterface`**
Returns `true` for four interface IDs: ERC-165, ERC-721, ERC-721 Metadata, and ERC-2981 (`0x2a55205a`). The addition of ERC-2981 here (absent from Genesis) signals to marketplaces that royalty information is available via `royaltyInfo`.

**Lines 177–186 — `_toString(uint256 v) → string`**
A compact uint-to-decimal-string conversion utility, written inline because there is no import for OpenZeppelin's `Strings` library. Algorithm:
1. If `v == 0`, return `"0"` immediately.
2. Count the number of decimal digits by dividing until zero.
3. Allocate a `bytes` array of that length.
4. Fill the array right-to-left: take `v % 10` to get the last digit, add 48 (ASCII `'0'`) to get the character code, assign, then divide by 10 and repeat.
5. Cast to `string` and return.

**Lines 188–190 — `IERC721Receiver` interface**
A minimal inline interface declaration for the ERC-721 receiver callback. Replaces an import. Defines only `onERC721Received` — the one function `_checkOnERC721Received` needs to call.

---

## 2.4 State Variables

| Variable | Type | Storage Slot Behavior | Visibility | Mutable | Who Can Change |
|---|---|---|---|---|---|
| `name` | `string` | No slot — compile-time constant | `public` | No | Nobody |
| `symbol` | `string` | No slot — compile-time constant | `public` | No | Nobody |
| `owner` | `address` | Immutable — code section | `public` | No | Nobody |
| `_baseURI` | `string` | Slot 0 | `private` | Yes | `owner` via `setBaseURI` |
| `MAX_SUPPLY` | `uint256` | No slot — compile-time constant | `public` | No | Nobody |
| `PRICE` | `uint256` | No slot — compile-time constant | `public` | No | Nobody |
| `ROYALTY_BPS` | `uint96` | No slot — compile-time constant | `public` | No | Nobody |
| `totalSupply` | `uint256` | Slot 1 | `public` | Yes | `mint()` (increments by 1) |
| `_ownerOf` | `mapping(uint256 => address)` | Dynamic — base slot 2 | `private` | Yes | `_transfer` |
| `_balanceOf` | `mapping(address => uint256)` | Dynamic — base slot 3 | `private` | Yes | `mint()`, `_transfer` |
| `_approved` | `mapping(uint256 => address)` | Dynamic — base slot 4 | `private` | Yes | `approve`, `_transfer` (deleted) |
| `_operatorApprovals` | `mapping(address => mapping(address => bool))` | Dynamic nested — base slot 5 | `private` | Yes | `setApprovalForAll` |

---

## 2.5 Functions

### `constructor(address owner_, string memory baseURI_)`

| Attribute | Value |
|---|---|
| Visibility | N/A (constructor) |
| Payable | No |
| State changes | Sets `owner`, `_baseURI` |
| Reverts | `ZeroAddress()` if `owner_ == address(0)` |

**Description:** Sets ownership and base metadata URL. No minting occurs. `totalSupply` starts at 0.

---

### `mint() → uint256 tokenId`

| Attribute | Value |
|---|---|
| Visibility | `external payable` |
| Modifiers | None |
| Payable | Yes — requires exactly `0.1 ether` |
| State changes | Increments `totalSupply`, sets `_ownerOf[tokenId]`, increments `_balanceOf[msg.sender]`, forwards ETH to `owner` |
| Who calls it | Anyone |
| Returns | The minted `tokenId` (1 through 5) |
| Reverts | `SoldOut()` if `totalSupply >= 5`; `WrongPrice()` if `msg.value != 0.1 ether`; `WithdrawFailed()` if ETH forward to owner fails |
| Emits | `Transfer(address(0), msg.sender, tokenId)`, `Minted(msg.sender, tokenId)` |

**Description:** The public mint entrypoint. Validates supply cap and exact price, mints a token, and immediately forwards 0.1 ETH to `ghostkey.base.eth`. If the ETH forward fails, the entire transaction reverts — no token is minted and no ETH is lost.

---

### `remaining() → uint256`

| Attribute | Value |
|---|---|
| Visibility | `external view` |
| Returns | `MAX_SUPPLY - totalSupply` (0 to 5) |

**Description:** Convenience view. Returns the number of tokens still available to mint.

---

### `withdraw()`

| Attribute | Value |
|---|---|
| Visibility | `external` |
| Modifiers | `onlyOwner` |
| Reverts | `NotOwner()`, `WithdrawFailed()` |
| Emits | `Withdraw(owner, bal)` |
| State changes | Sends contract's ETH balance to `owner` |

**Description:** Emergency ETH recovery. In normal operation the contract holds 0 ETH (auto-forwarded on every mint). `withdraw` exists for edge cases where ETH enters the contract via `selfdestruct` from another contract.

---

### `setBaseURI(string calldata baseURI_)`

| Attribute | Value |
|---|---|
| Visibility | `external` |
| Modifiers | `onlyOwner` |
| Reverts | `NotOwner()` |
| State changes | Overwrites `_baseURI` |
| Emits | Nothing |

**Description:** Updates the base metadata URL. Change takes effect immediately for all `tokenURI` and `contractURI` calls. No event emitted, so off-chain indexers must poll or listen to other signals.

---

### `tokenURI(uint256 tokenId) → string`

| Attribute | Value |
|---|---|
| Visibility | `external view` |
| Reverts | `NonexistentToken()` if token not yet minted |
| Returns | `_baseURI + tokenId.toString() + ".json"` |

**Description:** Dynamically constructs the metadata URL per token. All tokens share the same base URL prefix; only the numeric suffix differs.

---

### `contractURI() → string`

| Attribute | Value |
|---|---|
| Visibility | `external view` |
| Returns | `_baseURI + "contract.json"` |

**Description:** OpenSea-compatible collection-level metadata URL. Not part of ERC-721 spec.

---

### `balanceOf(address a) → uint256`

| Attribute | Value |
|---|---|
| Visibility | `external view` |
| Reverts | `ZeroAddress()` |

---

### `ownerOf(uint256 tokenId) → address`

| Attribute | Value |
|---|---|
| Visibility | `external view` |
| Reverts | `NonexistentToken()` |

**Note:** `external` (not `public`) — cannot be called internally. Internal code uses `_ownerOf[tokenId]` directly.

---

### `approve(address to, uint256 tokenId)`

| Attribute | Value |
|---|---|
| Visibility | `external` |
| Reverts | `NonexistentToken()`, `NotApproved()` |
| Emits | `Approval(o, to, tokenId)` |

---

### `getApproved(uint256 tokenId) → address`

| Attribute | Value |
|---|---|
| Visibility | `external view` |
| Reverts | `NonexistentToken()` |

---

### `setApprovalForAll(address operator, bool approved)`

| Attribute | Value |
|---|---|
| Visibility | `external` |
| Emits | `ApprovalForAll(msg.sender, operator, approved)` |

---

### `isApprovedForAll(address o, address operator) → bool`

| Attribute | Value |
|---|---|
| Visibility | `external view` |

---

### `transferFrom(address from, address to, uint256 tokenId)`

| Attribute | Value |
|---|---|
| Visibility | `public` |
| Delegates to | `_transfer(from, to, tokenId)` |

---

### `safeTransferFrom(address from, address to, uint256 tokenId)`

| Attribute | Value |
|---|---|
| Visibility | `external` |
| Calls | `_transfer`, then `_checkOnERC721Received(from, to, tokenId, "")` |

---

### `safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data)`

| Attribute | Value |
|---|---|
| Visibility | `external` |
| Calls | `_transfer`, then `_checkOnERC721Received(from, to, tokenId, data)` |

---

### `_transfer(address from, address to, uint256 tokenId)` (internal)

| Attribute | Value |
|---|---|
| Visibility | `internal` |
| Reverts | `ZeroAddress()`, `NonexistentToken()`, `NotTokenOwner()`, `NotApproved()` |
| State changes | `_ownerOf[tokenId]`, `_balanceOf[from]`, `_balanceOf[to]`, deletes `_approved[tokenId]` |
| Emits | `Transfer(from, to, tokenId)` |

**Description:** Internal transfer implementation. Validates destination, existence, ownership, and authorization. Clears per-token approval. Updates balances and owner in an `unchecked` block (safe within the supply cap). Emits `Transfer`.

---

### `_checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory data)` (internal)

| Attribute | Value |
|---|---|
| Visibility | `internal` |
| Reverts | `NotApproved()` if `to` is a contract that doesn't return the correct selector |

**Description:** ERC-721 receiver safety check. Skips for EOAs (`to.code.length == 0`). For contracts, calls `onERC721Received` and validates the return value. Prevents tokens being stranded in contracts not built to hold ERC-721 tokens.

---

### `royaltyInfo(uint256, uint256 salePrice) → (address receiver, uint256 royaltyAmount)`

| Attribute | Value |
|---|---|
| Visibility | `external view` |
| Returns | `receiver = owner`, `royaltyAmount = (salePrice * 500) / 10000` |

**Description:** EIP-2981 royalty lookup. Token ID is ignored — all tokens have the same 5% royalty to `owner`. See Section 2.6 for arithmetic.

---

### `supportsInterface(bytes4 iid) → bool`

| Attribute | Value |
|---|---|
| Visibility | `external pure` |
| Returns | `true` for `0x01ffc9a7` (ERC-165), `0x80ac58cd` (ERC-721), `0x5b5e139f` (ERC-721 Metadata), `0x2a55205a` (ERC-2981) |

---

### `_toString(uint256 v) → string` (internal)

| Attribute | Value |
|---|---|
| Visibility | `internal pure` |
| Returns | Decimal string representation of `v` |

**Description:** Converts `uint256` to its base-10 decimal string. For `v = 3` returns `"3"`. For `v = 0` returns `"0"`. Used only by `tokenURI`.

---

## 2.6 Math

### Mint price

```
PRICE = 0.1 ether = 100_000_000_000_000_000 wei
                  = 1 × 10^17 wei
```

The `ether` suffix in Solidity is a compile-time multiplier: `1 ether = 1_000_000_000_000_000_000 wei` (1 × 10^18). Therefore `0.1 ether = 1 × 10^17` wei = 100,000,000,000,000,000 wei. This is a `uint256` constant embedded in bytecode.

If a caller sends anything other than exactly this amount, `WrongPrice()` reverts. There is no rounding, no tolerance band.

**Total revenue at full sell-out:**
```
5 × 0.1 ETH = 0.5 ETH = 500_000_000_000_000_000 wei
```

### ETH auto-forward in `mint()`

```solidity
(bool ok, ) = owner.call{value: msg.value}("");
```

`msg.value` carries the exact `0.1 ether` (validated above). This call forwards the full amount atomically in the same transaction. No fee is retained by the contract. If the call fails, the whole transaction reverts, meaning no partial state is committed.

### `remaining()` arithmetic

```
remaining = MAX_SUPPLY - totalSupply
          = 5 - totalSupply
```

Simple subtraction. `totalSupply` is bounded to [0, 5] by the `SoldOut` guard, so this result is always in [0, 5] and can never underflow.

### EIP-2981 royalty calculation

```
royaltyAmount = (salePrice × ROYALTY_BPS) / 10_000
              = (salePrice × 500) / 10_000
              = salePrice × 0.05
              = 5% of salePrice
```

**Basis points explained:** 1 basis point = 0.01%. Therefore 500 basis points = 5.00%. The denominator `10_000` represents 100% expressed in basis points.

**Worked example — sale at 1 ETH:**
```
salePrice    = 1_000_000_000_000_000_000 wei (1 ETH)
ROYALTY_BPS  = 500
10_000       = 10_000

royaltyAmount = (1_000_000_000_000_000_000 × 500) / 10_000
              = 500_000_000_000_000_000_000 / 10_000
              = 50_000_000_000_000_000 wei
              = 0.05 ETH
```

**Worked example — sale at 0.5 ETH:**
```
royaltyAmount = (500_000_000_000_000_000 × 500) / 10_000
              = 25_000_000_000_000_000 wei
              = 0.025 ETH
```

**Integer division truncation:** Because Solidity uses integer division, if `salePrice × 500` does not divide evenly by `10_000`, the result truncates (rounds down). For example:

```
salePrice    = 3 wei
royaltyAmount = (3 × 500) / 10_000 = 1500 / 10_000 = 0 wei (truncated)
```

For any `salePrice < 20 wei`, the royalty rounds down to `0`. For any `salePrice ≥ 20 wei`, the royalty is at least `1 wei`. This is standard behavior for on-chain royalty math and does not represent a bug.

**Important:** EIP-2981 is advisory — marketplaces read `royaltyInfo` and are expected to enforce the payment, but the contract itself cannot force marketplaces to honor it. The royalty is not deducted on-chain during NFT transfers; it is a convention adopted by compliant marketplaces (OpenSea, Blur with royalties enabled, etc.).

### Balance arithmetic in `_transfer`

```solidity
unchecked {
    _balanceOf[from] -= 1;
    _balanceOf[to]   += 1;
}
```

`unchecked` removes Solidity's built-in overflow/underflow guard to save gas. Safety argument:
- `_balanceOf[from] -= 1`: Safe because the prior check `o != from` already ensures `from` currently owns the token, and the only way to own a token is to have been minted or transferred one — meaning `_balanceOf[from] >= 1` is guaranteed at this point.
- `_balanceOf[to] += 1`: Safe because there are only 5 tokens total (`MAX_SUPPLY = 5`), so `_balanceOf[to]` can never approach `uint256` max.

### `tokenId = ++totalSupply` in `mint()`

```solidity
unchecked { tokenId = ++totalSupply; }
```

Pre-increment semantics: `totalSupply` is incremented first, then assigned to `tokenId`. First call: `totalSupply` goes from `0` to `1`, `tokenId = 1`. Fifth call: `totalSupply` goes from `4` to `5`, `tokenId = 5`. `unchecked` is safe because `totalSupply` cannot reach 5 when this line runs (the `SoldOut()` check immediately above rejects `totalSupply >= 5`).

---

## 2.7 Events

### `Transfer(address indexed from, address indexed to, uint256 indexed tokenId)`
- **When fired:** Inside `_transfer` on every token movement, and inside `mint()` on each new mint (where `from == address(0)`).
- **What it logs:** Previous holder, new holder, token ID.
- **All three parameters are indexed** — allows efficient log filtering by any combination.

### `Approval(address indexed owner, address indexed approved, uint256 indexed tokenId)`
- **When fired:** Inside `approve()`.
- **What it logs:** Token owner, newly approved address, token ID.

### `ApprovalForAll(address indexed owner, address indexed operator, bool approved)`
- **When fired:** Inside `setApprovalForAll()`.
- **What it logs:** Granting address, operator, and the boolean grant value.

### `Minted(address indexed to, uint256 indexed tokenId)`
- **When fired:** Inside `mint()`, immediately after `Transfer`. Fires only on primary mints — not on secondary transfers.
- **What it logs:** The minter's address and the new token ID.
- **Purpose:** Allows indexers to identify primary mints without the `from == address(0)` heuristic on `Transfer`.

### `Withdraw(address indexed to, uint256 amount)`
- **When fired:** Inside `withdraw()` after a successful ETH push to `owner`.
- **What it logs:** The destination address (`owner`) and the amount sent (in wei).
- **Note:** In normal operation (auto-forwarding on mint), `amount` would be `0` since the contract holds no ETH. This event is meaningful only in edge cases.

---

## 2.8 Security Invariants

1. **Total supply can never exceed 5.** `MAX_SUPPLY` is a compile-time constant. The `>= MAX_SUPPLY` check in `mint()` is the first operation executed, before any state change. Even if `totalSupply` somehow became 5 mid-call (not possible in single-threaded EVM execution), no 6th token would be assigned.

2. **Mint price is permanently 0.1 ETH.** `PRICE` is a compile-time constant. No function can change it. Any call with a different `msg.value` (including zero) reverts.

3. **`owner` is permanently `ghostkey.base.eth`.** Immutable with no setter. Cannot be changed after deployment.

4. **Mint proceeds forward atomically to `owner`.** The ETH forward is the last statement in `mint()` before return, and if it fails, the entire transaction reverts — no token is minted and no ETH is taken from the caller.

5. **No token can be burned.** `_transfer` rejects `to == address(0)` with `ZeroAddress()`. Both `safeTransferFrom` variants delegate to `_transfer`.

6. **Only the token owner or an authorized party can transfer.** Three-way check in `_transfer`: direct owner, per-token approved address, or operator.

7. **Approvals clear on transfer.** `delete _approved[tokenId]` runs before any state update in `_transfer`.

8. **`withdraw` and `setBaseURI` are owner-only.** Both protected by the `onlyOwner` modifier.

9. **Safe transfer to contracts is validated.** `_checkOnERC721Received` prevents tokens being sent to contracts that don't implement the ERC-721 receiver interface.

10. **No reentrancy via mint's ETH forward.** The state updates (token assignment, balance increment) happen before the `owner.call{value:...}` on line 68. Even if `owner` were a malicious contract that reentered `mint()`, `totalSupply` is already incremented — the supply cap check would fail immediately (or a new legitimate token would be minted, consuming the reentrant caller's ETH). In practice, `owner` is a known EOA; this is moot.

---

# 3. Deploy.s.sol

## 3.1 Header

**File:** `script/Deploy.s.sol`
**Purpose:** Foundry deploy script for VaultfireGenesisNFT. Provides the exact `forge script` invocation needed to deploy and verify the Genesis NFT on Base mainnet.

**Note on discrepancy:** The Deploy.s.sol file as committed deploys `VaultfireGenesisNFT` using an older single-argument constructor signature (`new VaultfireGenesisNFT(uri)`), whereas the live contract constructor requires two arguments: `(address recipient_, string memory tokenURI_)`. The actual live deployment was performed via the Arachnid CREATE2 factory with the full two-argument constructor, using the salt `vaultfire-genesis-2026`. This script should be treated as a documentation artifact or a template for future use rather than the exact transcript of the live deployment. The authoritative deployment is verified on Basescan at address `0xdC5fE0da17864608eDC3c24A675E6C4C24a9832C`.

---

## 3.2 Full Source Code

```solidity
     1	// SPDX-License-Identifier: MIT
     2	pragma solidity 0.8.25;
     3	
     4	import "forge-std/Script.sol";
     5	import "../contracts/VaultfireGenesisNFT.sol";
     6	
     7	/// @notice Deploys VaultfireGenesisNFT to Base and auto-mints token #1 to deployer.
     8	/// Run:
     9	///   forge script script/Deploy.s.sol \
    10	///     --rpc-url base \
    11	///     --broadcast \
    12	///     --verify \
    13	///     --etherscan-api-key $BASESCAN_KEY \
    14	///     --account deployer
    15	contract Deploy is Script {
    16	    function run() external {
    17	        string memory uri = "https://theloopbreaker.com/nft/vaultfire-genesis.json";
    18	        vm.startBroadcast();
    19	        VaultfireGenesisNFT nft = new VaultfireGenesisNFT(uri);
    20	        vm.stopBroadcast();
    21	        console.log("VaultfireGenesisNFT deployed:", address(nft));
    22	        console.log("Token #1 owner:", nft.ownerOf(1));
    23	        console.log("Token URI:", nft.tokenURI(1));
    24	    }
    25	}
```

---

## 3.3 Line-by-Line Walkthrough

**Lines 1–2 — License and compiler version**
Standard header. Matches the contract files.

**Lines 4–5 — Imports**
- `forge-std/Script.sol` — provides the `Script` base contract and the `vm` cheatcode object, used to control broadcast (which transactions get signed and sent).
- `../contracts/VaultfireGenesisNFT.sol` — imports the contract being deployed.

**Lines 7–14 — NatSpec block with embedded run command**
Documents the intended usage. The `forge script` invocation:
- `--rpc-url base` — uses the `base` network alias from `foundry.toml`'s `[rpc_endpoints]`.
- `--broadcast` — actually submits transactions (without this, forge only simulates).
- `--verify` — submits source code for Basescan verification after deployment.
- `--etherscan-api-key $BASESCAN_KEY` — uses the `BASESCAN_KEY` environment variable for Basescan API access.
- `--account deployer` — uses the `deployer` keystore account (managed by `cast wallet`).

**Line 15 — `contract Deploy is Script`**
The deploy script is itself a Solidity contract. It inherits `Script` from forge-std, which provides access to `vm.*` cheatcodes (available only in the Foundry testing/scripting environment, not on-chain). `console.log` is also provided by forge-std.

**Line 16 — `function run() external`**
The entry point Foundry calls when running the script. By convention, Foundry scripts expose a `run()` function.

**Line 17 — `string memory uri = ...`**
Stores the metadata URL as a local string. This is the URL passed to the constructor as `tokenURI_`. In the actual live deployment, this URL was passed alongside the `recipient_` address.

**Lines 18–20 — `vm.startBroadcast()` / `vm.stopBroadcast()`**
The `vm.startBroadcast()` / `vm.stopBroadcast()` pair defines the transaction boundary. Any state-changing calls between these two lines are signed by the deployer's key and broadcast to the network. The `new VaultfireGenesisNFT(uri)` on line 19 is the only such call — it creates the contract in one transaction.

**Line 19 — `new VaultfireGenesisNFT(uri)`**
Deploys the contract using a standard `new` call (not CREATE2). This generates a standard CREATE address derived from `deployer + nonce`, not the deterministic CREATE2 address. The live contract at `0xdC5fE0da17864608eDC3c24A675E6C4C24a9832C` was deployed via the Arachnid CREATE2 factory — see Section 4 for the exact recipe.

**Lines 21–23 — `console.log` statements**
Print deployment diagnostics to the Foundry console (not on-chain). Confirms the deployed address, the owner of token #1 (who receives it in the constructor), and the token URI. These are sanity checks for the deployer.

---

# 4. Rebuild Recipe

This section provides the exact commands a developer would run to reproduce the live contracts from source — from environment setup through deterministic CREATE2 deployment.

## 4.1 Prerequisites

```bash
# Install Foundry (includes forge, cast, anvil)
curl -L https://foundry.paradigm.xyz | bash
foundryup

# Verify forge version (must support solc 0.8.25)
forge --version

# Install solc 0.8.25 via svm (Foundry manages this automatically)
# Or install manually:
# https://github.com/ethereum/solc-bin/
```

## 4.2 Project Setup

```bash
mkdir vaultfire-genesis-nft
cd vaultfire-genesis-nft
forge init --no-commit

# Place contracts at:
# contracts/VaultfireGenesisNFT.sol
# contracts/VaultfireFoundersEdition.sol
# script/Deploy.s.sol
```

## 4.3 foundry.toml Configuration

```toml
[profile.default]
src = "contracts"
out = "out"
libs = ["lib"]
solc_version = "0.8.25"
optimizer = true
optimizer_runs = 200
via_ir = true
evm_version = "cancun"

[rpc_endpoints]
base = "https://mainnet.base.org"
# Or use: base = "${BASE_RPC_URL}"

[etherscan]
base = { key = "${BASESCAN_KEY}", url = "https://api.basescan.org/api" }
```

## 4.4 Compile

```bash
forge build
# Expects: out/VaultfireGenesisNFT.sol/VaultfireGenesisNFT.json
#          out/VaultfireFoundersEdition.sol/VaultfireFoundersEdition.json
```

Verify compiler settings match the live deployment:
```bash
# solc version
forge config --profile default | grep solc_version
# → 0.8.25

# Check optimizer settings in foundry.toml:
# optimizer = true, optimizer_runs = 200, via_ir = true, evm_version = "cancun"
```

## 4.5 Encode Constructor Arguments

The Arachnid CREATE2 factory deploys contracts by prepending the constructor bytecode. You need to ABI-encode the constructor arguments and append them to the init bytecode.

**For VaultfireGenesisNFT:**

```bash
# Constructor: (address recipient_, string memory tokenURI_)
# recipient_  = 0xfA15Ee28939B222B0448261A22156070f0A7813C (ghostkey.base.eth)
# tokenURI_   = "https://theloopbreaker.com/nft/vaultfire-genesis.json"

cast abi-encode "constructor(address,string)" \
  0xfA15Ee28939B222B0448261A22156070f0A7813C \
  "https://theloopbreaker.com/nft/vaultfire-genesis.json"
```

**For VaultfireFoundersEdition:**

```bash
# Constructor: (address owner_, string memory baseURI_)
# owner_    = 0xfA15Ee28939B222B0448261A22156070f0A7813C (ghostkey.base.eth)
# baseURI_  = "https://theloopbreaker.com/nft/founders/"

cast abi-encode "constructor(address,string)" \
  0xfA15Ee28939B222B0448261A22156070f0A7813C \
  "https://theloopbreaker.com/nft/founders/"
```

## 4.6 Compute Deterministic Addresses (Verify Before Deploy)

The Arachnid CREATE2 factory address: `0x4e59b44847b379578588920ca78fbf26c0b4956c`

The CREATE2 address formula:
```
address = keccak256(0xff ‖ factory ‖ salt ‖ keccak256(initcode))[12:]
```
where `initcode = compiledBytecode ‖ abiEncodedConstructorArgs`.

```bash
# Genesis salt (bytes32)
GENESIS_SALT=0x7661756c74666972652d67656e657369732d3230323600000000000000000000

# Founders salt (bytes32)
FOUNDERS_SALT=0x7661756c74666972652d666f756e646572732d32303236000000000000000000

# Verify salt strings (ASCII decode):
# vaultfire-genesis-2026  → 0x7661756c74666972652d67656e657369732d323032360000...
# vaultfire-founders-2026 → 0x7661756c74666972652d666f756e646572732d323032360000...

python3 -c "print(bytes.fromhex('7661756c74666972652d67656e657369732d323032360000000000000000000000000000').decode())"
# → vaultfire-genesis-2026

python3 -c "print(bytes.fromhex('7661756c74666972652d666f756e646572732d3230323600000000000000000000').decode())"
# → vaultfire-founders-2026
```

Use `cast create2` to compute the expected address:

```bash
# Genesis
GENESIS_INITCODE=$(cat out/VaultfireGenesisNFT.sol/VaultfireGenesisNFT.json | jq -r '.bytecode.object')
GENESIS_ARGS=$(cast abi-encode "constructor(address,string)" \
  0xfA15Ee28939B222B0448261A22156070f0A7813C \
  "https://theloopbreaker.com/nft/vaultfire-genesis.json")
GENESIS_FULL_INITCODE="${GENESIS_INITCODE}${GENESIS_ARGS:2}"

cast compute-address \
  --factory 0x4e59b44847b379578588920ca78fbf26c0b4956c \
  --salt $GENESIS_SALT \
  --init-code-hash $(cast keccak $GENESIS_FULL_INITCODE)
# Expected: 0xdC5fE0da17864608eDC3c24A675E6C4C24a9832C
```

Repeat analogously for Founders. If the computed address matches the live address, bytecode is deterministically reproducible.

## 4.7 Deploy via Arachnid CREATE2 Factory

The Arachnid factory is at `0x4e59b44847b379578588920ca78fbf26c0b4956c` on Base mainnet. It accepts a single `bytes` calldata argument: the salt (first 32 bytes) followed by the init bytecode.

```bash
# Deploy VaultfireGenesisNFT
export PRIVATE_KEY=<your-key-or-use-cast-wallet>
export RPC_URL=https://mainnet.base.org

GENESIS_INITCODE=$(cat out/VaultfireGenesisNFT.sol/VaultfireGenesisNFT.json | jq -r '.bytecode.object')
GENESIS_ARGS=$(cast abi-encode "constructor(address,string)" \
  0xfA15Ee28939B222B0448261A22156070f0A7813C \
  "https://theloopbreaker.com/nft/vaultfire-genesis.json")
GENESIS_PAYLOAD="${GENESIS_SALT}${GENESIS_INITCODE:2}${GENESIS_ARGS:2}"

cast send \
  --rpc-url $RPC_URL \
  --private-key $PRIVATE_KEY \
  0x4e59b44847b379578588920ca78fbf26c0b4956c \
  $GENESIS_PAYLOAD
```

```bash
# Deploy VaultfireFoundersEdition
FOUNDERS_INITCODE=$(cat out/VaultfireFoundersEdition.sol/VaultfireFoundersEdition.json | jq -r '.bytecode.object')
FOUNDERS_ARGS=$(cast abi-encode "constructor(address,string)" \
  0xfA15Ee28939B222B0448261A22156070f0A7813C \
  "https://theloopbreaker.com/nft/founders/")
FOUNDERS_PAYLOAD="${FOUNDERS_SALT}${FOUNDERS_INITCODE:2}${FOUNDERS_ARGS:2}"

cast send \
  --rpc-url $RPC_URL \
  --private-key $PRIVATE_KEY \
  0x4e59b44847b379578588920ca78fbf26c0b4956c \
  $FOUNDERS_PAYLOAD
```

## 4.8 Verify on Basescan

```bash
# Verify VaultfireGenesisNFT
forge verify-contract \
  0xdC5fE0da17864608eDC3c24A675E6C4C24a9832C \
  contracts/VaultfireGenesisNFT.sol:VaultfireGenesisNFT \
  --chain-id 8453 \
  --compiler-version 0.8.25 \
  --num-of-optimizations 200 \
  --via-ir \
  --constructor-args $(cast abi-encode "constructor(address,string)" \
    0xfA15Ee28939B222B0448261A22156070f0A7813C \
    "https://theloopbreaker.com/nft/vaultfire-genesis.json") \
  --etherscan-api-key $BASESCAN_KEY \
  --watch
```

```bash
# Verify VaultfireFoundersEdition
forge verify-contract \
  0x1bcD9B7Cad06D2BbC7FdedC46B32eFd1988e28cE \
  contracts/VaultfireFoundersEdition.sol:VaultfireFoundersEdition \
  --chain-id 8453 \
  --compiler-version 0.8.25 \
  --num-of-optimizations 200 \
  --via-ir \
  --constructor-args $(cast abi-encode "constructor(address,string)" \
    0xfA15Ee28939B222B0448261A22156070f0A7813C \
    "https://theloopbreaker.com/nft/founders/") \
  --etherscan-api-key $BASESCAN_KEY \
  --watch
```

## 4.9 Post-Deploy Verification

```bash
# Confirm Genesis token #1 owner
cast call 0xdC5fE0da17864608eDC3c24A675E6C4C24a9832C \
  "ownerOf(uint256)(address)" 1 \
  --rpc-url $RPC_URL
# Expected: 0xfA15Ee28939B222B0448261A22156070f0A7813C

# Confirm Genesis token URI
cast call 0xdC5fE0da17864608eDC3c24A675E6C4C24a9832C \
  "tokenURI(uint256)(string)" 1 \
  --rpc-url $RPC_URL
# Expected: https://theloopbreaker.com/nft/vaultfire-genesis.json

# Confirm Founders supply (0 before any mint, up to 5)
cast call 0x1bcD9B7Cad06D2BbC7FdedC46B32eFd1988e28cE \
  "totalSupply()(uint256)" \
  --rpc-url $RPC_URL

# Confirm Founders price
cast call 0x1bcD9B7Cad06D2BbC7FdedC46B32eFd1988e28cE \
  "PRICE()(uint256)" \
  --rpc-url $RPC_URL
# Expected: 100000000000000000

# Confirm Founders royalty (sale of 1 ETH)
cast call 0x1bcD9B7Cad06D2BbC7FdedC46B32eFd1988e28cE \
  "royaltyInfo(uint256,uint256)(address,uint256)" 1 1000000000000000000 \
  --rpc-url $RPC_URL
# Expected: (0xfA15Ee28939B222B0448261A22156070f0A7813C, 50000000000000000)
# i.e. ghostkey.base.eth receives 0.05 ETH on a 1 ETH sale

# Confirm both contracts support ERC-2981 (Founders only)
cast call 0x1bcD9B7Cad06D2BbC7FdedC46B32eFd1988e28cE \
  "supportsInterface(bytes4)(bool)" 0x2a55205a \
  --rpc-url $RPC_URL
# Expected: true

# Confirm Genesis does NOT declare ERC-2981
cast call 0xdC5fE0da17864608eDC3c24A675E6C4C24a9832C \
  "supportsInterface(bytes4)(bool)" 0x2a55205a \
  --rpc-url $RPC_URL
# Expected: false
```

---

## 4.10 Summary of Live Deployment Facts

| Parameter | Genesis | Founders |
|---|---|---|
| Contract name | VaultfireGenesisNFT | VaultfireFoundersEdition |
| Symbol | VFGEN | VFFE |
| Chain | Base mainnet (8453) | Base mainnet (8453) |
| Address | `0xdC5fE0da17864608eDC3c24A675E6C4C24a9832C` | `0x1bcD9B7Cad06D2BbC7FdedC46B32eFd1988e28cE` |
| Owner | `0xfA15Ee28939B222B0448261A22156070f0A7813C` (ghostkey.base.eth) | `0xfA15Ee28939B222B0448261A22156070f0A7813C` (ghostkey.base.eth) |
| Deploy factory | `0x4e59b44847b379578588920ca78fbf26c0b4956c` (Arachnid CREATE2) | `0x4e59b44847b379578588920ca78fbf26c0b4956c` (Arachnid CREATE2) |
| Salt (bytes32) | `0x7661756c74666972652d67656e657369732d3230323600000000000000000000` | `0x7661756c74666972652d666f756e646572732d32303236000000000000000000` |
| Salt (ASCII) | `vaultfire-genesis-2026` | `vaultfire-founders-2026` |
| Supply | 1/1 (already minted) | 0–5 (public mint) |
| Mint price | None (no public mint) | 0.1 ETH exactly |
| Token #1 owner | ghostkey.base.eth | First caller of mint() |
| Royalties | None (no ERC-2981) | 5% (500 bps) to ghostkey.base.eth |
| Proceeds | N/A | Auto-forwarded to ghostkey.base.eth on every mint |
| Compiler | solc 0.8.25 | solc 0.8.25 |
| Optimizer runs | 200 | 200 |
| via-ir | Yes | Yes |
| EVM target | cancun | cancun |
| Dependencies | None | None |
| Standards | ERC-165, ERC-721, ERC-721 Metadata | ERC-165, ERC-721, ERC-721 Metadata, ERC-2981 |

---

*End of Vaultfire Live NFT Contracts — Technical Reference*
*Codex Batch 1 · Generated from source at /home/user/workspace/vaultfire-genesis-nft/*
