Catalog / module / nft
Burn-to-mint NFT
v1Burn your token to mint an NFT.
What it does
burn-mint-nft v1: Burn your token to mint an NFT
An ERC-721 collection that people mint by paying with a token (and/or ETH). At mint time the payment is split along up to 4 **routes** that are fixed at deploy:
| Route | What happens to its share | |---|---| | **BURN** | `payToken.burn(amount)`: a real burn. The mint reverts unless total supply *and* the contract's balance both fall by exactly that amount. | | **WALLET** | Token: sent straight to the wallet. ETH: credited to `owedNative[wallet]`; anyone can call `withdraw(wallet)` to pay it out. | | **INSTANCE** | Token: forwarded into another Socket instance's `fund(poolId, amount)` (e.g. a staking vault), fee-exempt. |
ETH payments only go to WALLET routes, split pro rata by their shares. The last route always takes the remainder, so a split never leaves dust.
Also included: up to 8 timed **phases** (public, or a merkle allowlist), each with an ETH and/or token price, a per-wallet limit and a phase cap. There is an owner **reserve** held back from every phase, an ERC-2981 royalty (≤10%), an optional ERC-721C transfer validator, an optional **soulbound** mode (ERC-5192) and ERC-4906 metadata refresh events.
Config (`BurnMintNFTConfig`)
`name`, `symbol`, `maxSupply` (≥1), `reserve` (≤ maxSupply), `baseURI`, `uriSuffix` (".json" or ""), `payToken` (0 = ETH or free only), `phases` (≤8), `routes` (≤4, shares sum to 100%), `royaltyReceiver`, `royaltyBps` (≤1000), `transferValidator` (0 = off), `soulbound`, `metadataWriter` (0 = none).
**Locked after deploy:** everything except `baseURI` (until frozen) and `transferValidator`. Routes, prices, phases, supply, reserve and royalty can only change by redeploying.
Merkle leaf (OZ StandardMerkleTree, types `address,uint256,uint256,address,uint256`): `keccak256(bytes.concat(keccak256(abi.encode(nft, chainid, phaseId, wallet, allowance))))`. A proof only works on this contract, this chain and this phase. In a merkle phase a wallet can mint up to its allowance (or the phase's wallet limit if that is lower).
Roles
- **Owner:** `reserveMint`, `setBaseURI` (until `freezeMetadata`, one way), `freezeMetadata`, `setTransferValidator` (0 always restores plain transfers), `pause`/`unpause` (minting only), `rescue` (only tokens above custody, never owed ETH). - **Metadata writer** (optional): may only call `emitMetadataUpdate(id)` (ERC-4906 refresh for changing traits). - **Anyone:** `mint`, `mintWithToken`, `withdraw(wallet)` (pays the recorded wallet), `settle()`, `burn` (own or approved NFTs). - **A route wallet:** `withdrawTo(dest)` to receive its owed ETH elsewhere (for wallets that cannot take ETH).
Fee lanes
| Flow | When | |---|---| | MintProceeds (default 1%) | On every paid mint, on the full price, before the split. Tokens sent straight to the contract pay the same fee when `settle()` routes them. |
The module is published with `FLAG_CHARGES_INFLOW`: an INSTANCE route's target pulls from this contract and does not charge again. ETH sent outside a mint is never routed. It stays above custody and can be rescued.
Risks in plain English
- Routes are locked. If a WALLET route's wallet is lost, so is its share. A wallet that cannot receive ETH can still `withdrawTo` somewhere else. - A pay token that cannot really burn makes token mints with a BURN route revert. Test with a small mint first. - If an INSTANCE target is paused, halted, migrated or full, token mints revert until it reopens. ETH mints keep working. If *this* NFT is migrated or its module halted, the exemption lapses and `settle()` forwards would be charged again by the target. - A transfer validator can block transfers while it is set. The owner can always set it to 0. - Soulbound tokens can never be sold, transferred or staked. They can be burned. - Until `freezeMetadata`, the owner can repoint all metadata.
What if
- **My payout wallet cannot receive ETH?** It stays owed. That wallet calls `withdrawTo(dest)`. Nobody else can move it. - **The token's burn is fake (supply does not fall)?** The mint reverts with `FakeBurn` and nothing is spent. - **The vault I route to is paused?** Token mints revert (`EntriesClosed`) until it reopens; no tokens are lost. - **Someone sends tokens straight to the NFT?** `settle()` (anyone) charges the fee and splits them along the routes. - **Someone tries a proof from another collection, phase or chain?** It fails: the leaf binds all three.
Implementation notes (size)
The module sits at the EIP-170 limit next to the Socket base, so:
- `LeanERC721` is a minimal ERC-721 core with the same semantics as OpenZeppelin 5's `_update` hook and argless errors. It replaces OZ `ERC721`. ERC-2981 is a single `royaltyInfo`. - The **kit** (`BurnMintNFTPresets`, which is also the Presets contract) does the pure config work: decoding, validation, flattening into packed words (`PackLib`), name/symbol/tokenURI strings and the current-config re-encode. The implementation deploys it in its constructor (its first CREATE). Each clone derives its address from its own EIP-1167 code: `kit() == CREATE(implementation, nonce 1)`. It is only ever called with view calls and holds nothing. - `BurnMintNFTLens` serves phase status (`phaseInfo`, `phases`) and `paymentInfo` from `config()`, `info()` and the counters. - `name()`, `symbol()` and `tokenURI()` are views that go through the kit; they cost more gas but are free in `eth_call`.
Files
`BurnMintNFT.sol` (module), `BurnMintNFTTypes.sol` (structs, PackLib, kit interface), `BurnMintNFTPresets.sol` (kit + friendly presets: whole-token prices, gwei, percent routes, start time + hours, royalty per-mille), `BurnMintNFTLens.sol`, `LeanERC721.sol`, `ui-schema.json`.
Risks in plain English
- Routes are locked at deploy. If a route's wallet is lost, its share is lost too (ETH can be redirected by that wallet with withdrawTo).
- BURN routes call the pay token's burn() and check that total supply really fell; a token that cannot burn makes token mints revert, so test with a small mint first.
- A vault route forwards tokens into another Socket contract. If that contract is paused, halted or full, token mints revert until it reopens; ETH mints keep working.
- A transfer validator is an outside contract that can block transfers. You can always remove it, but while it is set it decides which transfers go through.
- Soulbound NFTs can never be sold, transferred or staked. Holders can still burn them.
- Until you freeze metadata, you can repoint every token's image and traits.
What if…
- What if my payout wallet cannot receive ETH?
- The ETH stays owed to it in the contract. That wallet can call withdrawTo to send it anywhere else. Nobody else can take it.
- What if the token I burn is not really burnable?
- Every burn is checked against the token's total supply and the contract's own balance. If the supply did not fall by exactly the amount, the mint is cancelled and nothing is spent.
- What if the vault I route to is paused?
- Token mints revert with EntriesClosed until the vault reopens; nobody loses tokens. ETH mints are unaffected because ETH only goes to wallet routes.
- What if someone sends tokens straight to the NFT contract?
- Anyone can call settle(): the tokens pay the same 1% fee as a mint and are then split along your routes, burn share included.
- Can an allowlist proof be reused on another collection or phase?
- No. Each leaf includes this contract's address, the chain id and the phase id, so a proof only works where it was made for.
Fees
| Lane | Fee |
|---|---|
| Mint paymentson every paid mint (ETH or token), on the full price, before it is split | 1%already at the hard maximum |
| Mint paymentswhen settle() routes tokens that were sent to the contract directly | 1%already at the hard maximum |
Fees are fixed into each contract at deploy and can never change for it. Claims, withdrawals and refunds are never charged. Hard cap 1%, written into the code.
Evidence
- Form check
- Built from the source files
- Implementation
- —
- Code hash
- —
Settings you choose at deploy
- Collection nameLocked forever
- SymbolLocked forever
- Total supplyFixed forever. Includes the owner reserve.Locked forever
- Owner reserveNFTs only you can mint (team, giveaways). Held back from every phase.Locked forever
- Metadata folderToken N reads <folder>N.json. You can change it until you freeze metadata.Changeable
- File endingLocked forever
- Token people burn / pay withLeave empty for an ETH-only or free mint. Must have totalSupply(); BURN routes also need burn(uint256).Locked forever
- Mint phasesUp to 8 windows. A phase with a merkle root is an allowlist.Locked forever
- Where the mint money goesShares must add up to 100%. ETH payments only go to WALLET routes.Locked forever
- Royalty receiverLocked forever
- RoyaltyLocked forever
- ERC-721C transfer validatorEnforces creator royalties on marketplaces. Leave empty to keep transfers unrestricted; you can turn it off any time.Changeable
- Soulbound (cannot be transferred)Locked forever
- Metadata refresherOptional address that may only signal marketplaces to refresh one token's metadata (for changing traits).Locked forever