← Back to list

Soft burn: the one decision that was actually a decision

The other four features I added had no real design space. The standards specified everything.

Roy in DEXignation · 2026-06-07 23:59 · 0 claps · 1.6 min read
#web3 #ethereum #en #polygon #solidity
Open on Medium ↗
Wiki topics: CRY · Crypto & Web3 🔭 · Astronomy & Space

Soft burn: the one decision that was actually a decision

The other four features I added had no real design space. The standards specified everything.

Burn was different. The standards say nothing about it. I had to think.

The problem: when a .dex domain expires and nobody renews, the ERC-721 token continues to exist on-chain. Its ownerOf() reverts (because I made expired tokens return address(0) instead of the previous owner), but OpenSea and other marketplaces still index it. The expired name shows up as a “ghost” listing of the previous holder, who has no incentive to clean it up.

Three options:

A. Restricted burn — only the previous holder can call burn(id) after the grace period.

B. Permissionless burn — anyone can call burn(id) once the grace period has fully passed.

C. Automatic burn during re-registration — when someone re-registers the expired name, the old token gets _burned implicitly.

I had already implemented ©. It works but only fires when re-registration happens. If nobody ever re-registers, the ghost listing persists forever.

(A) is the “safer” option in terms of audit surface. But the previous holder has no reason to spend gas cleaning up a domain they let lapse. The whole point of cleanup is that someone else cares (a marketplace indexer, a community member running cleanup scripts, a competitive name service trying to make my domain pollution look bad).

I went with (B). Permissionless. Anyone can burn an expired .dex domain once expiry + GRACE_PERIOD < block.timestamp. The only risk would be if available() returned true for a non-expired token — and I had to convince myself, in writing, that it never does. (It doesn’t. The function literally checks block.timestamp > expires + GRACE_PERIOD.)

Total cost: 30 lines of contract code, one new event, one new error type.

function burn(uint256 id) external override {
 address prevOwner = _ownerOf(id);
 if (prevOwner == address(0)) {
 revert TokenOwnerNotFound();
 }
 if (!available(id)) {
 revert NotYetBurnable(id, expiries[id] + GRACE_PERIOD + 1);
 }

_burn(id);
 delete expiries[id];
 delete names[id];

emit NameBurned(id, prevOwner);
}

This is the part of my code I’m proudest of this week. Not because it’s clever — it’s not. But because the alternative (“you must call burn yourself, sorry”) would have made me feel responsible for an ecosystem-wide cleanup problem that I have neither the authority nor the incentive to solve.

Permissionless cleanup means the community can solve it without my permission. That feels right.


메타데이터
post_id
2a4dcb9c134e
slug
soft-burn-the-one-decision-that-was-actually-a-decision-2a4dcb9c134e
url
https://medium.com/dexignation/soft-burn-the-one-decision-that-was-actually-a-decision-2a4dcb9c134e
canonical_url
https://medium.com/dexignation/soft-burn-the-one-decision-that-was-actually-a-decision-2a4dcb9c134e
author_url
https://medium.com/@punditcode
status
ok
fetched_at
2026-06-09 21:21:26