{"componentChunkName":"component---src-templates-blog-post-js","path":"/building-aeons/","result":{"data":{"markdownRemark":{"id":"ed5f05bf-520a-57e1-afaa-9ec6926060f6","html":"<p>Aeons is an Internet Art Experience on the Base blockchain. With fully onchain art, metadata and gamification, the project can exist without any external pieces. But even with fully onchain ideals, these offchain pieces add greatly to the experience.</p>\n<p>This article is a technical description of the code I wrote for <a href=\"https://aeons.xyz\">Aeons</a>. The first half is the contract suite on <a href=\"https://www.base.org/\">Base</a>. It holds the art, the metadata and the rules, and it needs nothing else. The second half is the services that sit outside the chain.</p>\n<p><span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 760px;\"\n    >\n      <a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/a6b3bfd7e29dc69b701e1d6e44b31a9b/5ab88/aeons-gallery.jpg\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 67.60219646125687%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAOABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAECBf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAcCkyRB//8QAFxAAAwEAAAAAAAAAAAAAAAAAABEhQf/aAAgBAQABBQLIzHT/xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAWEAEBAQAAAAAAAAAAAAAAAAAQEQD/2gAIAQEABj8CZqf/xAAcEAACAgIDAAAAAAAAAAAAAAABEQAhEFExQXH/2gAIAQEAAT8hr1CUUgtxx6uXO4Acf//aAAwDAQACAAMAAAAQgM//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/ED//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/ED//xAAaEAEBAQEBAQEAAAAAAAAAAAABEQAhMUGR/9oACAEBAAE/EBoRcWw4BGu5d4ofC+a4iPSd/dTYJOmSt3//2Q=='); background-size: cover; display: block;\"\n  ></span>\n  <img\n        class=\"gatsby-resp-image-image\"\n        alt=\"The Aeons gallery\"\n        title=\"The Aeons gallery\"\n        src=\"/static/a6b3bfd7e29dc69b701e1d6e44b31a9b/31642/aeons-gallery.jpg\"\n        srcset=\"/static/a6b3bfd7e29dc69b701e1d6e44b31a9b/d053c/aeons-gallery.jpg 190w,\n/static/a6b3bfd7e29dc69b701e1d6e44b31a9b/622bd/aeons-gallery.jpg 380w,\n/static/a6b3bfd7e29dc69b701e1d6e44b31a9b/31642/aeons-gallery.jpg 760w,\n/static/a6b3bfd7e29dc69b701e1d6e44b31a9b/a7625/aeons-gallery.jpg 1140w,\n/static/a6b3bfd7e29dc69b701e1d6e44b31a9b/6e796/aeons-gallery.jpg 1520w,\n/static/a6b3bfd7e29dc69b701e1d6e44b31a9b/5ab88/aeons-gallery.jpg 1639w\"\n        sizes=\"(max-width: 760px) 100vw, 760px\"\n        loading=\"lazy\"\n      />\n  </a>\n    </span></p>\n<p>16,584 Aeons were minted.</p>\n<p>Each token is a fully onchain animation with a limited life. A token starts with four days of life. If the holder does nothing, the token dies and the system burns it. To add life to a token, the holder must merge it with other tokens. That one rule makes a token change its state with time only, and it gives real work to every layer of the stack.</p>\n<h2 id=\"how-the-project-works\" style=\"position:relative;\"><a href=\"#how-the-project-works\" aria-label=\"how the project works permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>How the project works</h2>\n<p>The rules of the collection control every technical decision that comes after them. So it is best to know the rules first.</p>\n<p>Each token moves through six stages. If a token has more days of life, its stage is higher:</p>\n<p><span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 760px;\"\n    >\n      <a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/4b46b450aafe058f6722e51a347be66d/f33bc/aeons-stages.jpg\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 17.77173913043478%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAEABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAIF/8QAFQEBAQAAAAAAAAAAAAAAAAAAAQD/2gAMAwEAAhADEAAAAcChAb//xAAWEAEBAQAAAAAAAAAAAAAAAAAAARH/2gAIAQEAAQUCalf/xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAWEAEBAQAAAAAAAAAAAAAAAAAAMQH/2gAIAQEABj8CTEf/xAAaEAACAgMAAAAAAAAAAAAAAAABEQAxIVFh/9oACAEBAAE/IXwXAqyI6iBSf//aAAwDAQACAAMAAAAQiC//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/ED//xAAWEQEBAQAAAAAAAAAAAAAAAAAAATH/2gAIAQIBAT8QiY//xAAcEAACAgIDAAAAAAAAAAAAAAABEQAhMXFRYaH/2gAIAQEAAT8QCgByZkeanMwN5blA3sif/9k='); background-size: cover; display: block;\"\n  ></span>\n  <img\n        class=\"gatsby-resp-image-image\"\n        alt=\"The six stages of an Aeon\"\n        title=\"The six stages of an Aeon\"\n        src=\"/static/4b46b450aafe058f6722e51a347be66d/31642/aeons-stages.jpg\"\n        srcset=\"/static/4b46b450aafe058f6722e51a347be66d/d053c/aeons-stages.jpg 190w,\n/static/4b46b450aafe058f6722e51a347be66d/622bd/aeons-stages.jpg 380w,\n/static/4b46b450aafe058f6722e51a347be66d/31642/aeons-stages.jpg 760w,\n/static/4b46b450aafe058f6722e51a347be66d/a7625/aeons-stages.jpg 1140w,\n/static/4b46b450aafe058f6722e51a347be66d/6e796/aeons-stages.jpg 1520w,\n/static/4b46b450aafe058f6722e51a347be66d/f33bc/aeons-stages.jpg 1840w\"\n        sizes=\"(max-width: 760px) 100vw, 760px\"\n        loading=\"lazy\"\n      />\n  </a>\n    </span></p>\n<ul>\n<li><strong>Nebula</strong> — 0 to 15 days. A token starts here, with 4 days.</li>\n<li><strong>Plasma</strong> — 16 to 63 days.</li>\n<li><strong>Gamma</strong> — 64 to 255 days.</li>\n<li><strong>Solstice</strong> — 256 to 1023 days.</li>\n<li><strong>Neutra</strong> — 1024 to 2047 days.</li>\n<li><strong>Zenith</strong> — 2048 days or more. This is 5.6 years of life.</li>\n</ul>\n<p>A holder can do three operations with a token:</p>\n<ul>\n<li><strong>Collide</strong> — merge one token into a second token. The second token survives and receives the days of life of the first token. It moves up through the stages. The system burns the first token.</li>\n<li><strong>Simulate</strong> — see the result of a collision before you do it.</li>\n<li><strong>Immortalize</strong> — remove a token from death permanently. The token keeps its current stage.</li>\n</ul>\n<p>If the holder does nothing, the days of life of a token decrease. If a token goes above a stage limit, its stage increases. If a token goes below a stage limit, its stage decreases. When the days of life become zero, the token is dead.</p>\n<p><span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 760px;\"\n    >\n      <a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/876845c29364448053c2c2122822e9ad/dde36/aeons-lifecycle.jpg\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 55.952380952380956%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAIF/8QAFQEBAQAAAAAAAAAAAAAAAAAAAQD/2gAMAwEAAhADEAAAAcRJAn//xAAUEAEAAAAAAAAAAAAAAAAAAAAg/9oACAEBAAEFAl//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAUEAEAAAAAAAAAAAAAAAAAAAAg/9oACAEBAAY/Al//xAAZEAADAAMAAAAAAAAAAAAAAAAAAREQUZH/2gAIAQEAAT8h6RDK9leP/9oADAMBAAIAAwAAABD3D//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8QP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8QP//EAB0QAQACAgIDAAAAAAAAAAAAAAEAMREhQZFhcYH/2gAIAQEAAT8QDFq1SRQpPRADpfpNFu55HuZXmf/Z'); background-size: cover; display: block;\"\n  ></span>\n  <img\n        class=\"gatsby-resp-image-image\"\n        alt=\"The lifecycle of an Aeon\"\n        title=\"The lifecycle of an Aeon\"\n        src=\"/static/876845c29364448053c2c2122822e9ad/31642/aeons-lifecycle.jpg\"\n        srcset=\"/static/876845c29364448053c2c2122822e9ad/d053c/aeons-lifecycle.jpg 190w,\n/static/876845c29364448053c2c2122822e9ad/622bd/aeons-lifecycle.jpg 380w,\n/static/876845c29364448053c2c2122822e9ad/31642/aeons-lifecycle.jpg 760w,\n/static/876845c29364448053c2c2122822e9ad/a7625/aeons-lifecycle.jpg 1140w,\n/static/876845c29364448053c2c2122822e9ad/6e796/aeons-lifecycle.jpg 1520w,\n/static/876845c29364448053c2c2122822e9ad/dde36/aeons-lifecycle.jpg 1680w\"\n        sizes=\"(max-width: 760px) 100vw, 760px\"\n        loading=\"lazy\"\n      />\n  </a>\n    </span></p>\n<p>The state of the collection is never constant. A smart contract has no scheduler, and it has no idea of “later”. So the primary problem is this: a token must become older and die. The contract can do the former. The offchain services fulfill the latter with a burn call.</p>\n<h2 id=\"the-stack\" style=\"position:relative;\"><a href=\"#the-stack\" aria-label=\"the stack permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>The stack</h2>\n<p>Aeons has four repositories:</p>\n<ul>\n<li><strong>aeons-web-app</strong> — <a href=\"https://nextjs.org/\">Next.js</a>. UI and front end.</li>\n<li><strong>aeons-contracts</strong> — <a href=\"https://soliditylang.org/\">Solidity</a>, <a href=\"https://hardhat.org/\">Hardhat</a>, <a href=\"https://viem.sh/\">viem</a>.</li>\n<li><strong>aeons-indexer</strong> — <a href=\"https://ponder.sh/\">Ponder</a>.</li>\n<li><strong>aeons-listener</strong> — <a href=\"https://nodejs.org/\">Node</a>, <a href=\"https://bullmq.io/\">BullMQ</a>, <a href=\"https://pptr.dev/\">Puppeteer</a>.</li>\n</ul>\n<p>The contract is the source of truth, and it can run alone. Every other layer watches it and keeps the outside world in agreement with it. None of those layers can change the rules of the game.</p>\n<p><span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 760px;\"\n    >\n      <a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/167422ff615fc3145527d040eb251897/dde36/aeons-architecture.jpg\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 59.523809523809526%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAMABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAQAF/8QAFQEBAQAAAAAAAAAAAAAAAAAAAQD/2gAMAwEAAhADEAAAAcUBKKf/xAAUEAEAAAAAAAAAAAAAAAAAAAAg/9oACAEBAAEFAl//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAUEAEAAAAAAAAAAAAAAAAAAAAg/9oACAEBAAY/Al//xAAZEAEAAwEBAAAAAAAAAAAAAAABABExECH/2gAIAQEAAT8hcI0OTyXFvn//2gAMAwEAAgADAAAAEF/P/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPxA//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPxA//8QAGxAAAwACAwAAAAAAAAAAAAAAAAERECExYYH/2gAIAQEAAT8QWdkEQsIe2in4M5Y//9k='); background-size: cover; display: block;\"\n  ></span>\n  <img\n        class=\"gatsby-resp-image-image\"\n        alt=\"Aeons system architecture\"\n        title=\"Aeons system architecture\"\n        src=\"/static/167422ff615fc3145527d040eb251897/31642/aeons-architecture.jpg\"\n        srcset=\"/static/167422ff615fc3145527d040eb251897/d053c/aeons-architecture.jpg 190w,\n/static/167422ff615fc3145527d040eb251897/622bd/aeons-architecture.jpg 380w,\n/static/167422ff615fc3145527d040eb251897/31642/aeons-architecture.jpg 760w,\n/static/167422ff615fc3145527d040eb251897/a7625/aeons-architecture.jpg 1140w,\n/static/167422ff615fc3145527d040eb251897/6e796/aeons-architecture.jpg 1520w,\n/static/167422ff615fc3145527d040eb251897/dde36/aeons-architecture.jpg 1680w\"\n        sizes=\"(max-width: 760px) 100vw, 760px\"\n        loading=\"lazy\"\n      />\n  </a>\n    </span></p>\n<p>The description below starts at the chain and moves out.</p>\n<h2 id=\"the-smart-contracts\" style=\"position:relative;\"><a href=\"#the-smart-contracts\" aria-label=\"the smart contracts permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>The smart contracts</h2>\n<p>This is the half that stands alone. The art, the metadata and the rules are all in the contract suite.</p>\n<p>The token is an ERC-721, but it is not a standard one. <code class=\"language-text\">Aeons.sol</code> uses <strong>ERC721A</strong> for cheap batch mints and sequential IDs. It uses the <code class=\"language-text\">ERC721AC</code> variant from Limit Break’s <a href=\"https://github.com/limitbreakinc/creator-token-contracts\">creator-token-contracts</a>, which adds onchain royalty control and a transfer validator. For ownership, the contract uses <a href=\"https://github.com/Vectorized/solady\">Solady</a>’s <code class=\"language-text\">Ownable</code>, which uses less gas. The royalties use the standard <code class=\"language-text\">IERC2981</code>. The contract is <a href=\"https://basescan.org/address/0x185fbb7597ef4a2f55faf70302e196e642c4a673#code\">verified on Base</a>, thus the full suite is public.</p>\n<p>The most important design decision is this: the contract stores no dynamic token data.</p>\n<p>The stage, the traits, the colours, the days of life and the alive/dead status of a token are not fields in a struct. The contract calculates them at each read, from two facts: the time when the token dies, and the random value of the epoch of the mint.</p>\n<h3 id=\"time-and-the-six-stages\" style=\"position:relative;\"><a href=\"#time-and-the-six-stages\" aria-label=\"time and the six stages permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Time, and the six stages</h3>\n<p>Each token stores a <code class=\"language-text\">deathTime</code>. This is an absolute Unix timestamp, not a countdown. The contract calculates the stage from the number of days before that time:</p>\n<div class=\"gatsby-highlight\" data-language=\"solidity\"><pre class=\"language-solidity\"><code class=\"language-solidity\">function getStageIndex(\n    AeonsStorage.Aeon memory aeon\n) public view returns (uint256) {\n    // If the Aeon is in stasis, return the stored stage index\n    if (aeon.inStasis) {\n        return aeon.stageIndex;\n    }\n\n    uint256 deathTime = aeon.deathTime;\n    uint256 blockTime = block.timestamp;\n\n    if (deathTime &lt;= blockTime) {\n        return 0; // Nebula (Death variant)\n    }\n\n    uint256 daysUntilDeath = AeonsUtils.day(blockTime, deathTime);\n\n    if (daysUntilDeath &lt; 16) {\n        return 0; // Nebula\n    } else if (daysUntilDeath &lt; 64) {\n        return 1; // Plasma\n    } else if (daysUntilDeath &lt; 256) {\n        return 2; // Gamma\n    } else if (daysUntilDeath &lt; 1024) {\n        return 3; // Solstice\n    } else if (daysUntilDeath &lt; 2048) {\n        return 4; // Neutra\n    }\n\n    return 5; // Zenith\n}</code></pre></div>\n<p>The full state of the game comes from <code class=\"language-text\">deathTime</code> and <code class=\"language-text\">block.timestamp</code>. A token can move up, move down and die with no transaction. Time does the work.</p>\n<h3 id=\"collide\" style=\"position:relative;\"><a href=\"#collide\" aria-label=\"collide permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Collide</h3>\n<p>A collision is the only way to stay alive. It moves the life from one token to a second token, then it burns the first token:</p>\n<div class=\"gatsby-highlight\" data-language=\"solidity\"><pre class=\"language-solidity\"><code class=\"language-solidity\">aeons.all[tokenId].collisions.push(burnTokenId);\n\n// We know original deathTimes are derived from mintEndTime + 4 days\nuint256 burnTokenDaysUntilDeath = AeonsUtils.day(\n    block.timestamp,\n    aeons.all[burnTokenId].deathTime\n);\naeons.all[tokenId].deathTime += burnTokenDaysUntilDeath * 24 hours;\n\n// Set the death time to the current block timestamp\n// This is to ensure that the token is dead immediately after collision\naeons.all[burnTokenId].deathTime = block.timestamp;\n\naeons.collisions += 1;\naeons.burned += 1;\n\n_burn(burnTokenId);</code></pre></div>\n<p>The token that survives keeps its own traits and receives all the days of life of the other token. A holder can transfer only the days that a token still has. So an early collision banks the most life. <code class=\"language-text\">collideMany</code> does this operation in a batch. <code class=\"language-text\">collideSim</code> and <code class=\"language-text\">collideManySim</code> are the <code class=\"language-text\">view</code> equivalents that supply the simulate function. They give the result of a collision, but they change no data and they spend nothing. These <code class=\"language-text\">view</code> functions are used on the app to see how a collision would affect an Aeon before cementing with a write call.</p>\n<h3 id=\"immortalize\" style=\"position:relative;\"><a href=\"#immortalize\" aria-label=\"immortalize permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Immortalize</h3>\n<p>Even a Zenith token, with 5.6 years of life, will die in the end. Only an immortalization stops this. <code class=\"language-text\">stasis()</code> is a one-way <code class=\"language-text\">payable</code> function that removes a token from death permanently. The fee is the interesting part:</p>\n<div class=\"gatsby-highlight\" data-language=\"solidity\"><pre class=\"language-solidity\"><code class=\"language-solidity\">uint256 stageMultiplier = 1; // Default to stage 0 multiplier\nif (stageIndex == 1) {\n    stageMultiplier = 4;\n} else if (stageIndex == 2) {\n    stageMultiplier = 8;\n} else if (stageIndex == 3) {\n    stageMultiplier = 16;\n} else if (stageIndex == 4) {\n    stageMultiplier = 32;\n} else if (stageIndex == 5) {\n    stageMultiplier = 64;\n}\n\nuint256 scarcityMultiplier = 1 ether;\nif (activeSupply &gt; 0) {\n    uint256 stasisPercentage = (aeons.stasis * 1 ether) / activeSupply;\n    uint256 scarcityFactor = (9 * stasisPercentage * stasisPercentage) /\n        1 ether;\n    scarcityMultiplier = 1 ether + scarcityFactor;\n}\n\nreturn (stasisBaseFee * stageMultiplier * scarcityMultiplier) / 1 ether;</code></pre></div>\n<p>The fee is the base fee multiplied by the stage multiplier and the scarcity multiplier. The base fee is <code class=\"language-text\">0.001 ETH</code>. The stage multiplier increases linearly with the rarity of the stage. The scarcity multiplier increases <strong>quadratically</strong> with the part of the supply that is already immortal. The <code class=\"language-text\">9 * pct²</code> term goes from 1x, when no token is immortal, to 10x, when all tokens are immortal.</p>\n<p>So it is cheap to keep a token early. It gets more expensive to keep a late-stage token after other holders start to lock in theirs:</p>\n<table>\n<thead>\n<tr>\n<th>Stage</th>\n<th>Multiplier</th>\n<th>@ 0% immortalized</th>\n<th>@ 50%</th>\n<th>@ 100%</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>1 — Nebula</td>\n<td>×1</td>\n<td>0.001 ETH</td>\n<td>0.00325 ETH</td>\n<td>0.01 ETH</td>\n</tr>\n<tr>\n<td>2 — Plasma</td>\n<td>×4</td>\n<td>0.004 ETH</td>\n<td>0.013 ETH</td>\n<td>0.04 ETH</td>\n</tr>\n<tr>\n<td>3 — Gamma</td>\n<td>×8</td>\n<td>0.008 ETH</td>\n<td>0.026 ETH</td>\n<td>0.08 ETH</td>\n</tr>\n<tr>\n<td>4 — Solstice</td>\n<td>×16</td>\n<td>0.016 ETH</td>\n<td>0.052 ETH</td>\n<td>0.16 ETH</td>\n</tr>\n<tr>\n<td>5 — Neutra</td>\n<td>×32</td>\n<td>0.032 ETH</td>\n<td>0.104 ETH</td>\n<td>0.32 ETH</td>\n</tr>\n<tr>\n<td>6 — Zenith</td>\n<td>×64</td>\n<td>0.064 ETH</td>\n<td>0.208 ETH</td>\n<td>0.64 ETH</td>\n</tr>\n</tbody>\n</table>\n<h3 id=\"supply-is-a-result-not-a-constant\" style=\"position:relative;\"><a href=\"#supply-is-a-result-not-a-constant\" aria-label=\"supply is a result not a constant permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Supply is a result, not a constant</h3>\n<p>Most NFT collections have a fixed supply. Aeons does not have one. The demand set the quantity at the mint, and from the moment the mint stopped, the quantity only falls. Each collision and each death burns a token. Each immortalization locks a token into the group that survives. The supply is the running total of every operation the holders do.</p>\n<p>This is why the stasis fee changes with time. The fee uses the <em>ratio</em> of the supply that is immortal, and that ratio increases from two directions. The quantity of tokens that can burn decreases as tokens collide. The quantity of immortal tokens increases as holders lock theirs in. So the cost of immortality increases on its own.</p>\n<p>The graph below shows the effect for some demand conditions. It includes the 16,584 tokens that the holders minted:</p>\n<p><span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 760px;\"\n    >\n      <a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/9449eda5e04a134413f6ea659b8c8f49/dde36/aeons-supply-fee.jpg\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 70.23809523809524%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAOABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAEF/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAH/2gAMAwEAAhADEAAAAcMVAf/EABQQAQAAAAAAAAAAAAAAAAAAACD/2gAIAQEAAQUCX//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABQQAQAAAAAAAAAAAAAAAAAAACD/2gAIAQEABj8CX//EABYQAAMAAAAAAAAAAAAAAAAAAAERIP/aAAgBAQABPyFmv//aAAwDAQACAAMAAAAQAA//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/ED//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/ED//xAAaEAEAAwADAAAAAAAAAAAAAAABABEhQWFx/9oACAEBAAE/EMDZbyj7EqBkp6i3P//Z'); background-size: cover; display: block;\"\n  ></span>\n  <img\n        class=\"gatsby-resp-image-image\"\n        alt=\"How supply and the immortality fee move over time\"\n        title=\"How supply and the immortality fee move over time\"\n        src=\"/static/9449eda5e04a134413f6ea659b8c8f49/31642/aeons-supply-fee.jpg\"\n        srcset=\"/static/9449eda5e04a134413f6ea659b8c8f49/d053c/aeons-supply-fee.jpg 190w,\n/static/9449eda5e04a134413f6ea659b8c8f49/622bd/aeons-supply-fee.jpg 380w,\n/static/9449eda5e04a134413f6ea659b8c8f49/31642/aeons-supply-fee.jpg 760w,\n/static/9449eda5e04a134413f6ea659b8c8f49/a7625/aeons-supply-fee.jpg 1140w,\n/static/9449eda5e04a134413f6ea659b8c8f49/6e796/aeons-supply-fee.jpg 1520w,\n/static/9449eda5e04a134413f6ea659b8c8f49/dde36/aeons-supply-fee.jpg 1680w\"\n        sizes=\"(max-width: 760px) 100vw, 760px\"\n        loading=\"lazy\"\n      />\n  </a>\n    </span></p>\n<p>The mint quantities are examples, but the result is not what you expect. The fee uses the ratio and not the quantity. So the same scarcity effect appears if 5,000 tokens mint or if 40,000 tokens mint. The demand sets the initial size of the collection. The holders set the cost to stay in it.</p>\n<p>To see the current data of the collection, use the <a href=\"https://aeons.xyz/observatory\">observatory</a>. It shows how many tokens survive, how many tokens are immortal, and how much life they have.</p>\n<h3 id=\"death\" style=\"position:relative;\"><a href=\"#death\" aria-label=\"death permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Death</h3>\n<p>A token is dead when its clock becomes zero:</p>\n<div class=\"gatsby-highlight\" data-language=\"solidity\"><pre class=\"language-solidity\"><code class=\"language-solidity\">function isDead(uint256 tokenId) public view returns (bool) {\n    AeonsStorage.Aeon memory aeon = aeons.all[tokenId];\n    return !aeon.inStasis &amp;&amp; aeon.deathTime &lt;= block.timestamp;\n}</code></pre></div>\n<p>A dead token stays in the supply until an account burns it. The contract cannot burn a token by itself when the death time passes, because a contract cannot start a transaction. Some external account must send that transaction. So <code class=\"language-text\">burnDead</code> is permissionless, and any account can call it on any dead token. If an account burns the token of a different owner, the contract records it:</p>\n<div class=\"gatsby-highlight\" data-language=\"solidity\"><pre class=\"language-solidity\"><code class=\"language-solidity\">function burnDead(uint256 tokenId) public {\n    if (!isDead(tokenId)) {\n        revert AeonNotDead();\n    }\n\n    address owner = ownerOf(tokenId);\n    address burner = msg.sender;\n\n    // If called by non-owner of the token, increment total killed\n    if (owner != burner) {\n        aeons.killed += 1;\n    }\n\n    // All calls are token burns, so increment total burned\n    aeons.burned += 1;\n\n    _burn(tokenId);\n\n    emit Death(tokenId, owner, burner);\n}</code></pre></div>\n<p>This makes the removal of dead tokens an open task, and it gives other holders a reason to do it. <code class=\"language-text\">_beforeTokenTransfers</code> also blocks transfers of dead tokens. So a holder cannot sell a dead token, and an account can only burn it. To call <code class=\"language-text\">burnDead</code> for thousands of tokens is a task for the offchain services. The section below shows how.</p>\n<h3 id=\"onchain-randomness\" style=\"position:relative;\"><a href=\"#onchain-randomness\" aria-label=\"onchain randomness permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Onchain randomness</h3>\n<p>The contract assigns the traits (Prism, Energy, Velocity and the colours) with a <strong>commit-reveal</strong> procedure. This procedure comes from <a href=\"https://www.checks.art/\">Checks</a>. The random value applies to each epoch, not to each token, and no user can predict it at the mint.</p>\n<p><span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 760px;\"\n    >\n      <a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/1fc68a447d78124f081a80471341e1c6/dde36/aeons-commit-reveal.jpg\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 51.1904761904762%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAKABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAIBAwX/xAAVAQEBAAAAAAAAAAAAAAAAAAAAAf/aAAwDAQACEAMQAAABxWrLJFD/xAAYEAACAwAAAAAAAAAAAAAAAAAAARESIP/aAAgBAQABBQIqxqMf/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAFhAAAwAAAAAAAAAAAAAAAAAAACAx/9oACAEBAAY/AiL/AP/EABgQAQEBAQEAAAAAAAAAAAAAAAERACAh/9oACAEBAAE/IbhPSzIok4//2gAMAwEAAgADAAAAEJ/f/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPxA//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPxA//8QAGhABAAIDAQAAAAAAAAAAAAAAAQARECExgf/aAAgBAQABPxClHA9gIRObm8NV1gdRn//Z'); background-size: cover; display: block;\"\n  ></span>\n  <img\n        class=\"gatsby-resp-image-image\"\n        alt=\"Onchain randomness — commit and reveal\"\n        title=\"Onchain randomness — commit and reveal\"\n        src=\"/static/1fc68a447d78124f081a80471341e1c6/31642/aeons-commit-reveal.jpg\"\n        srcset=\"/static/1fc68a447d78124f081a80471341e1c6/d053c/aeons-commit-reveal.jpg 190w,\n/static/1fc68a447d78124f081a80471341e1c6/622bd/aeons-commit-reveal.jpg 380w,\n/static/1fc68a447d78124f081a80471341e1c6/31642/aeons-commit-reveal.jpg 760w,\n/static/1fc68a447d78124f081a80471341e1c6/a7625/aeons-commit-reveal.jpg 1140w,\n/static/1fc68a447d78124f081a80471341e1c6/6e796/aeons-commit-reveal.jpg 1520w,\n/static/1fc68a447d78124f081a80471341e1c6/dde36/aeons-commit-reveal.jpg 1680w\"\n        sizes=\"(max-width: 760px) 100vw, 760px\"\n        loading=\"lazy\"\n      />\n  </a>\n    </span></p>\n<p>Each mint calls <code class=\"language-text\">resolveEpochIfNecessary()</code>. If the current epoch has no commit, the function selects a block approximately 50 blocks in the future. That block is the source of the random value. When the chain goes past that block, the next mint reveals the value:</p>\n<div class=\"gatsby-highlight\" data-language=\"solidity\"><pre class=\"language-solidity\"><code class=\"language-solidity\">} else if (block.number &gt; currentEpoch.revealBlock) {\n    // Epoch has been committed and is within range to be revealed\n    // Set its randomness to the target block hash\n    currentEpoch.randomness = uint128(\n        uint256(\n            keccak256(\n                abi.encodePacked(\n                    blockhash(currentEpoch.revealBlock),\n                    block.prevrandao\n                )\n            )\n        ) % (2 ** 128 - 1)\n    );\n\n    currentEpoch.revealed = true;\n\n    emit CommitReveal.NewEpoch(aeons.epoch, currentEpoch.revealBlock);\n    metadataUpdateAll();\n\n    ++aeons.epoch;\n\n    resolveEpochIfNecessary();\n}</code></pre></div>\n<p>Each token calculates its own seed from the random value of the epoch: <code class=\"language-text\">keccak256(randomness, tokenId)</code>. The contract then compares the seed with fixed cumulative probability tables to select the traits. It writes no data, and it calculates the traits at each read. The rarity of the Energy trait is an example:</p>\n<div class=\"gatsby-highlight\" data-language=\"solidity\"><pre class=\"language-solidity\"><code class=\"language-solidity\">/**\n * @dev Get the cumulative probabilities of an energy being selected\n * @return The cumulative probabilities of the energies traits\n */\nfunction energiesProbabilities() public pure returns (uint8[7] memory) {\n    // 1%, 7%, 13%, 15%, 18%, 23%, 23%\n    return [1, 8, 21, 36, 54, 77, 100];\n}</code></pre></div>\n<p>Ivora is the rarest Energy, and it lands 1% of the time. A token is “revealed” when the random value of its epoch is more than zero. Before that time, the token shows a white dormant form.</p>\n<h3 id=\"fully-onchain-art\" style=\"position:relative;\"><a href=\"#fully-onchain-art\" aria-label=\"fully onchain art permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Fully onchain art</h3>\n<p>The art is also onchain. <code class=\"language-text\">tokenURI</code> always assembles the JSON metadata onchain as a base64 <code class=\"language-text\">data:application/json</code> string. If no external URL is set, <code class=\"language-text\">AeonsRenderer</code> makes a complete HTML page for the animation. It uses <a href=\"https://github.com/intartnft/scripty.sol\">scripty.sol</a> and <a href=\"https://github.com/holic/ethfs\">ethfs</a> to read a gzipped <a href=\"https://threejs.org/\">three.js</a>, fflate and the animation script of the project from onchain storage. Then it adds the data of the token.</p>\n<p>A static image is the difficult part of a fully onchain project. The contract has a manual onchain SVG fallback for each stage. It also has a dormant form for a token before its epoch reveals:</p>\n<p><span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 760px;\"\n    >\n      <a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/cc59b51d65a81f2000ce7d396da1da5e/b4133/aeons-fallback.jpg\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAIDBv/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAefpRmAAD//EABgQAAIDAAAAAAAAAAAAAAAAAAABEBEg/9oACAEBAAEFAioQ3n//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/AR//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/AR//xAAUEAEAAAAAAAAAAAAAAAAAAAAw/9oACAEBAAY/Ah//xAAZEAACAwEAAAAAAAAAAAAAAAAAASAhMUH/2gAIAQEAAT8hEGbsRyP/2gAMAwEAAgADAAAAEMMAAP/EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQMBAT8QH//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQIBAT8QH//EABoQAAMAAwEAAAAAAAAAAAAAAAABESExUSD/2gAIAQEAAT8QSpTeF0SNoiai6PYuayN1+P/Z'); background-size: cover; display: block;\"\n  ></span>\n  <img\n        class=\"gatsby-resp-image-image\"\n        alt=\"A dormant, pre-reveal Aeon\"\n        title=\"A dormant, pre-reveal Aeon\"\n        src=\"/static/cc59b51d65a81f2000ce7d396da1da5e/31642/aeons-fallback.jpg\"\n        srcset=\"/static/cc59b51d65a81f2000ce7d396da1da5e/d053c/aeons-fallback.jpg 190w,\n/static/cc59b51d65a81f2000ce7d396da1da5e/622bd/aeons-fallback.jpg 380w,\n/static/cc59b51d65a81f2000ce7d396da1da5e/31642/aeons-fallback.jpg 760w,\n/static/cc59b51d65a81f2000ce7d396da1da5e/b4133/aeons-fallback.jpg 1080w\"\n        sizes=\"(max-width: 760px) 100vw, 760px\"\n        loading=\"lazy\"\n      />\n  </a>\n    </span></p>\n<p>The renderer is behind an interface, and you can replace it (<code class=\"language-text\">ERC-165</code> controls this). So you can upgrade or move the art, and you never touch the token contract. The SVG fallback keeps the project complete with no external help. But it is not the real animation. To put the three.js piece on a marketplace card is a job for the offchain services.</p>\n<h3 id=\"minting\" style=\"position:relative;\"><a href=\"#minting\" aria-label=\"minting permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Minting</h3>\n<p>The mint continued for 24 hours, and the demand set the supply. Each token started with the same quantity of life:</p>\n<div class=\"gatsby-highlight\" data-language=\"solidity\"><pre class=\"language-solidity\"><code class=\"language-solidity\">// Give every token a starting life time of 4 days from the mint end\naeons.all[tokenId].deathTime = mintEndTime + 4 days;</code></pre></div>\n<p>Each token had four days on the clock from the moment the mint stopped. Then the collection started to become older, and the layers outside the chain started to follow it.</p>\n<h2 id=\"the-indexer\" style=\"position:relative;\"><a href=\"#the-indexer\" aria-label=\"the indexer permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>The indexer</h2>\n<p>The contract knows everything, but it answers one question at a time. A profile page, a status filter and an activity list all need the same thing: the onchain data in a database. The indexer puts it there. It adds nothing to the rules of the game. It makes the collection easy to look at.</p>\n<p>The indexer uses <a href=\"https://ponder.sh/\">Ponder</a>, and it monitors the contract on Base. The procedure is the same for each event: read the full state from the contract, then copy it into Postgres. The <code class=\"language-text\">Birth</code> handler is an example:</p>\n<div class=\"gatsby-highlight\" data-language=\"ts\"><pre class=\"language-ts\"><code class=\"language-ts\">ponder<span class=\"token punctuation\">.</span><span class=\"token function\">on</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Aeons:Birth\"</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\"><span class=\"token punctuation\">{</span> event<span class=\"token punctuation\">,</span> context <span class=\"token punctuation\">}</span></span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> client <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> context<span class=\"token punctuation\">;</span>\n\n    <span class=\"token keyword\">const</span> enrichedAeon <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> client<span class=\"token punctuation\">.</span><span class=\"token function\">readContract</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n        abi<span class=\"token punctuation\">:</span> <span class=\"token constant\">CONTRACT_ABI</span><span class=\"token punctuation\">,</span>\n        address<span class=\"token punctuation\">:</span> <span class=\"token constant\">CONTRACT_ADDRESS</span><span class=\"token punctuation\">,</span>\n        functionName<span class=\"token punctuation\">:</span> <span class=\"token constant\">CONTRACT_ENRICHED_AEON_FUNCTION_NAME</span><span class=\"token punctuation\">,</span>\n        args<span class=\"token punctuation\">:</span> <span class=\"token punctuation\">[</span>event<span class=\"token punctuation\">.</span>args<span class=\"token punctuation\">.</span>tokenId<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token keyword\">await</span> context<span class=\"token punctuation\">.</span>db<span class=\"token punctuation\">.</span><span class=\"token function\">insert</span><span class=\"token punctuation\">(</span>aeons<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">values</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n        id<span class=\"token punctuation\">:</span> <span class=\"token function\">Number</span><span class=\"token punctuation\">(</span>event<span class=\"token punctuation\">.</span>args<span class=\"token punctuation\">.</span>tokenId<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n        owner<span class=\"token punctuation\">:</span> event<span class=\"token punctuation\">.</span>args<span class=\"token punctuation\">.</span>minter<span class=\"token punctuation\">,</span>\n        deathTime<span class=\"token punctuation\">:</span> enrichedAeon<span class=\"token punctuation\">.</span>deathTime<span class=\"token punctuation\">,</span>\n        stageIndex<span class=\"token punctuation\">:</span> enrichedAeon<span class=\"token punctuation\">.</span>stageIndex<span class=\"token punctuation\">,</span>\n        <span class=\"token comment\">// ...all traits, colors and status, mirrored from the chain</span>\n        image<span class=\"token punctuation\">:</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span><span class=\"token constant\">IMAGE_BASE_URI</span><span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>event<span class=\"token punctuation\">.</span>args<span class=\"token punctuation\">.</span>tokenId<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\">/dead.png</span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token comment\">// ...append to the activity feed</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>The indexer processes <code class=\"language-text\">Birth</code>, <code class=\"language-text\">Collision</code>, <code class=\"language-text\">Death</code>, <code class=\"language-text\">Stasis</code> and <code class=\"language-text\">NewEpoch</code> with the same procedure. The contract stays the source of truth, and Ponder makes it easy to query.</p>\n<p>The event model cannot do one thing: the stage of a token changes with time only, and time sends no event. So there is also a <strong>block handler</strong>. It operates at an interval, and it calculates the ages and the stages again:</p>\n<div class=\"gatsby-highlight\" data-language=\"ts\"><pre class=\"language-ts\"><code class=\"language-ts\">blocks<span class=\"token punctuation\">:</span> <span class=\"token punctuation\">{</span>\n    DailyUpdate<span class=\"token punctuation\">:</span> <span class=\"token punctuation\">{</span>\n      chain<span class=\"token punctuation\">:</span> <span class=\"token string\">\"base\"</span><span class=\"token punctuation\">,</span>\n      startBlock<span class=\"token punctuation\">:</span> <span class=\"token function\">Number</span><span class=\"token punctuation\">(</span>startBlock<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n      <span class=\"token comment\">// 24 hours * 60 minutes * 60 seconds / 2 seconds per block</span>\n      interval<span class=\"token punctuation\">:</span> <span class=\"token punctuation\">(</span><span class=\"token number\">24</span> <span class=\"token operator\">*</span> <span class=\"token number\">60</span> <span class=\"token operator\">*</span> <span class=\"token number\">60</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">/</span> <span class=\"token number\">2</span><span class=\"token punctuation\">,</span> <span class=\"token comment\">// = 43200 blocks</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>A small <a href=\"https://hono.dev/\">Hono</a> REST API operates above the database. It has purpose-built endpoints for the tokens of a holder, the activity feed, the dead token ids and the collection stats. The stats endpoint adds the live floor price and the live volume from OpenSea, and it supplies the data for the <a href=\"https://aeons.xyz/observatory\">observatory</a>. Ponder has a GraphQL layer, but a specific JSON API is better here, because the front end can use it directly. This API keeps the profile pages in agreement with the chain.</p>\n<h2 id=\"the-offchain-services\" style=\"position:relative;\"><a href=\"#the-offchain-services\" aria-label=\"the offchain services permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>The offchain services</h2>\n<p>The listener is the offchain engine of the project. It does two tasks that the chain will not do for itself. The collection is still correct without the listener, but it is much better to use with it.</p>\n<p><span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 760px;\"\n    >\n      <a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/66ee17a2ab5b5f47a2c862e89bdc9232/dde36/aeons-pipeline.jpg\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 45.238095238095234%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAJABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAECBf/EABUBAQEAAAAAAAAAAAAAAAAAAAAB/9oADAMBAAIQAxAAAAHizKwg/8QAFBABAAAAAAAAAAAAAAAAAAAAIP/aAAgBAQABBQJf/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAFBABAAAAAAAAAAAAAAAAAAAAIP/aAAgBAQAGPwJf/8QAGBAAAgMAAAAAAAAAAAAAAAAAAJEBIDH/2gAIAQEAAT8hQidp/9oADAMBAAIAAwAAABAH3//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8QP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8QP//EABsQAAICAwEAAAAAAAAAAAAAAAERADEQIVGB/9oACAEBAAE/EHoB0hXObi0XmDj/2Q=='); background-size: cover; display: block;\"\n  ></span>\n  <img\n        class=\"gatsby-resp-image-image\"\n        alt=\"Two jobs the chain will not do for itself\"\n        title=\"Two jobs the chain will not do for itself\"\n        src=\"/static/66ee17a2ab5b5f47a2c862e89bdc9232/31642/aeons-pipeline.jpg\"\n        srcset=\"/static/66ee17a2ab5b5f47a2c862e89bdc9232/d053c/aeons-pipeline.jpg 190w,\n/static/66ee17a2ab5b5f47a2c862e89bdc9232/622bd/aeons-pipeline.jpg 380w,\n/static/66ee17a2ab5b5f47a2c862e89bdc9232/31642/aeons-pipeline.jpg 760w,\n/static/66ee17a2ab5b5f47a2c862e89bdc9232/a7625/aeons-pipeline.jpg 1140w,\n/static/66ee17a2ab5b5f47a2c862e89bdc9232/6e796/aeons-pipeline.jpg 1520w,\n/static/66ee17a2ab5b5f47a2c862e89bdc9232/dde36/aeons-pipeline.jpg 1680w\"\n        sizes=\"(max-width: 760px) 100vw, 760px\"\n        loading=\"lazy\"\n      />\n  </a>\n    </span></p>\n<p><code class=\"language-text\">aeons-listener</code> is a Node service. It uses <a href=\"https://bullmq.io/\">BullMQ</a> with Redis. It monitors the contract with two methods: a <code class=\"language-text\">getLogs</code> backfill in fixed windows to get the old data, then a <code class=\"language-text\">watchEvent</code> poll to stay current. Each log that agrees with the filter becomes a job.</p>\n<h3 id=\"a-static-image-of-a-live-animation\" style=\"position:relative;\"><a href=\"#a-static-image-of-a-live-animation\" aria-label=\"a static image of a live animation permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>A static image of a live animation</h3>\n<p>Marketplaces need a PNG. An Aeon is a three.js particle animation, and the contract makes it onchain. To connect the two, the service must run the art in a browser and record one frame.</p>\n<p>A worker starts <a href=\"https://pptr.dev/\">Puppeteer</a> and loads the token page. It waits until the animation signals that it is ready, then it records the image:</p>\n<div class=\"gatsby-highlight\" data-language=\"ts\"><pre class=\"language-ts\"><code class=\"language-ts\"><span class=\"token keyword\">const</span> html <span class=\"token operator\">=</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">&lt;body style=\"margin:0;\"></span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>item<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\">&lt;/body></span><span class=\"token template-punctuation string\">`</span></span>\n<span class=\"token keyword\">const</span> dataUrl <span class=\"token operator\">=</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">data:text/html;base64;charset=UTF-8,</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>Buffer<span class=\"token punctuation\">.</span><span class=\"token keyword\">from</span><span class=\"token punctuation\">(</span>html<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">toString</span><span class=\"token punctuation\">(</span><span class=\"token string\">'base64'</span><span class=\"token punctuation\">)</span><span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span>\n<span class=\"token keyword\">await</span> page<span class=\"token punctuation\">.</span><span class=\"token function\">goto</span><span class=\"token punctuation\">(</span>dataUrl<span class=\"token punctuation\">)</span>\n\n<span class=\"token keyword\">try</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">await</span> page<span class=\"token punctuation\">.</span><span class=\"token function\">waitForFunction</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"RENDERED === true\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n    timeout<span class=\"token punctuation\">:</span> isStage5 <span class=\"token operator\">?</span> <span class=\"token number\">15000</span> <span class=\"token punctuation\">:</span> <span class=\"token number\">5000</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span> <span class=\"token keyword\">catch</span> <span class=\"token punctuation\">(</span>e<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span>\n\n<span class=\"token keyword\">const</span> image <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> page<span class=\"token punctuation\">.</span><span class=\"token function\">screenshot</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>The heavy stages need more time to become stable. A Zenith has many particles, thus its timeout is longer. The worker sends the image to <a href=\"https://vercel.com/docs/vercel-blob\">Vercel Blob</a> at a fixed path. Then it tells OpenSea to read the image again.</p>\n<p>The result: the moment a token collides, dies or becomes immortal, the service makes its image again and sends the update signal to marketplaces automatically.</p>\n<h3 id=\"burning-the-dead-tokens\" style=\"position:relative;\"><a href=\"#burning-the-dead-tokens\" aria-label=\"burning the dead tokens permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Burning the dead tokens</h3>\n<p><code class=\"language-text\">burnDead</code> is permissionless, but an account must still call it. The listener does this on a schedule.</p>\n<p>Burns go out in batches, behind a distributed lock:</p>\n<div class=\"gatsby-highlight\" data-language=\"ts\"><pre class=\"language-ts\"><code class=\"language-ts\"><span class=\"token keyword\">const</span> currentNonce <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> publicClient<span class=\"token punctuation\">.</span><span class=\"token function\">getTransactionCount</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n    address<span class=\"token punctuation\">,</span>\n    blockTag<span class=\"token punctuation\">:</span> <span class=\"token string\">'pending'</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">let</span> nextNonce <span class=\"token operator\">=</span> <span class=\"token function\">Number</span><span class=\"token punctuation\">(</span>currentNonce<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">for</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">const</span> batch <span class=\"token keyword\">of</span> batches<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">await</span> walletClient<span class=\"token punctuation\">.</span><span class=\"token function\">writeContract</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n        address<span class=\"token punctuation\">:</span> <span class=\"token constant\">AEONS_CONTRACT_ADDRESS</span><span class=\"token punctuation\">,</span>\n        abi<span class=\"token punctuation\">,</span>\n        functionName<span class=\"token punctuation\">:</span> <span class=\"token string\">'burnDeadMany'</span><span class=\"token punctuation\">,</span>\n        args<span class=\"token punctuation\">:</span> <span class=\"token punctuation\">[</span>batch<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n        account<span class=\"token punctuation\">:</span> walletClient<span class=\"token punctuation\">.</span>account<span class=\"token punctuation\">,</span>\n        nonce<span class=\"token punctuation\">:</span> nextNonce<span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    nextNonce<span class=\"token operator\">++</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>Balance checks ensure the service never sends a transaction that would fail.</p>\n<h2 id=\"bringing-it-together\" style=\"position:relative;\"><a href=\"#bringing-it-together\" aria-label=\"bringing it together permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Bringing it together</h2>\n<p>Aeons works because each layer has one task, and the boundaries between the layers are clean. The contract owns the art and the rules, and it is the single source of truth. The indexer copies that data into a database and supplies it as an API. The listener monitors the same events and does the two tasks that the chain will not do for itself: it makes an image of the onchain animation for the marketplaces, and it calls <code class=\"language-text\">burnDead</code> to remove the tokens that are dead. The front end reads the indexer and writes to the contract.</p>\n<p>The contract is Aeon’s source of truth. So most of the engineering work is the machinery that keeps the app, the indexer and the marketplaces in agreement with it. That machinery is not the art, and the art does not need it. But it’s what turns the art into a complete experience.</p>\n<p>The <a href=\"https://aeons.xyz\">collection lives here</a>. The <a href=\"https://aeons.xyz/observatory\">observatory</a> shows its state in real time. The <a href=\"https://basescan.org/address/0x185fbb7597ef4a2f55faf70302e196e642c4a673#code\">contract is verified on Base</a>.</p>\n<p>Aeons credits:</p>\n<ul>\n<li><a href=\"https://tr.af/\">Traf</a> — art direction, UI and front end.</li>\n<li><a href=\"https://x.com/jackbutcher\">Jack Butcher</a>, <a href=\"https://x.com/jalilwahdat\">Jalil</a> and Traf — Checks and EightyColors.</li>\n<li><a href=\"https://x.com/marka_eth\">Marka</a> and Jalil — review of the Aeons contracts.</li>\n<li><a href=\"https://x.com/_MouseDev\">MouseDev</a> — commit-reveal idea.</li>\n<li><a href=\"https://x.com/frolic\">frolic</a> — ethfs.</li>\n<li><a href=\"https://x.com/0xthedude\">thedude</a> and <a href=\"https://x.com/xtremetom\">xtremetom</a> — scripty.sol.</li>\n<li>The fully onchain art community.</li>\n</ul>","excerpt":"Aeons is an Internet Art Experience on the Base blockchain. With fully onchain art, metadata and gamification, the project can exist without any external pieces…","frontmatter":{"title":"Building Aeons: A Full Stack Digital Art Project","date":"10th September 2025","featured_image":{"childImageSharp":{"fluid":{"base64":"data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAOABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAECBf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAcCkyRB//8QAFxAAAwEAAAAAAAAAAAAAAAAAABEhQf/aAAgBAQABBQLIzHT/xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAWEAEBAQAAAAAAAAAAAAAAAAAQEQD/2gAIAQEABj8CZqf/xAAcEAACAgIDAAAAAAAAAAAAAAABEQAhEFExQXH/2gAIAQEAAT8hr1CUUgtxx6uXO4Acf//aAAwDAQACAAMAAAAQgM//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/ED//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/ED//xAAaEAEBAQEBAQEAAAAAAAAAAAABEQAhMUGR/9oACAEBAAE/EBoRcWw4BGu5d4ofC+a4iPSd/dTYJOmSt3//2Q==","tracedSVG":"data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20width='400'%20height='270'%3e%3cpath%20d='M0%20135v135h401V0H0v135m183-90c1%201%200%201-2%201h-3l2%201c1%200%202%200%201%201-2%200-1%207%201%207l1%202%201%201c2%200%201%203-1%204-3%201-3%201-5-2-2-2-2-2-2%200l-1%202-2%203c-2%203-2%203%200%203l1-1c0-2%203-2%205-1%203%202%205%204%204%206%200%202%201%201%203-1h2l4%202%203%203%203%202%202%202h2c3-1%206-1%206%201l-4%201-3%201%202%201%204%201%202%201h3l1-1%202-4c1-3%202-4%201-5l-1-5c-1-3-1-3-1-1-1%201-2%202-3%201l-1-2-1-1v-1l2-1-2-1v-1l2-3%202-1h1v-2l1%201%201%201%201-1%201-2c4-2%204-10%200-9l-3-1c-2-2-6-3-8-2-2%203-6%202-6%200h-2l-3%203c-2%200-2%200%200%201%201%201%201%201-1%201s-3-1-3-3c0-3-2-4-2-1l-1%205-1-1-1-1c-1%200-2-1-1-3l-1-3c-2%200-2%201-2%202m-6%20114l1%204%202%203h-3c-3%200-3%204%201%208%202%203%203%203%206%203%204-1%207%201%206%203v2c1%202%201%202%202%201%202-2%202-2%204%200l2%205%2010%202h9l-4-3-4-3-4-2-3-1v-11c1-11%200-12-3-10-2%201-3%203%200%203l-1%202c-2%202-2%202-3%201l-3-1-3-2-4-3-1-1h-7m-93%207l-7%207c-2%204-3%209-1%209l1-4%201-3c1%201%202%200%203-2%201-4%2010-9%2010-6l-4%205c-4%203-4%204-5%2010v7c2%201%200%203-2%202-2%200-2%200-1%202%202%204%2012%208%2015%207%201%200%201-4-1-5-1-1%200-1%203-1%203%201%204%201%204-1l2-3v-6c0-2%200-2%201%201l1%203%201-3v-6l-2-4-4-4c-5-3-6-5-4-5l1-1c-2-2-8-1-12%201m215%203l-2%203-2%202-2%202%206-3c1-3%205-2%206%200v2l2%201c2%200%203%201%203%204l-1%205-1%203-2%204v4l1%202c-1%201%202%204%203%203%201%200%202-1%201-2%200-3%200-3%202-2%202%200%202%200%201-1l-2-1-1-2%201-2c1%201%203-4%202-6v-5c1-7%201-9-1-10l-4-1c-5-2-8-2-10%200m-28%2019c-3%204-4%204-3%209%200%203%201%206%203%208l3%203v-2l1-5%201-4c1-1%201-1-1-1h-3l4-6c5-6%201-7-5-2'%20fill='%23d3d3d3'%20fill-rule='evenodd'/%3e%3c/svg%3e","aspectRatio":1.4792418772563176,"src":"/static/a6b3bfd7e29dc69b701e1d6e44b31a9b/bc3a8/aeons-gallery.jpg","srcSet":"/static/a6b3bfd7e29dc69b701e1d6e44b31a9b/d278e/aeons-gallery.jpg 200w,\n/static/a6b3bfd7e29dc69b701e1d6e44b31a9b/8539d/aeons-gallery.jpg 400w,\n/static/a6b3bfd7e29dc69b701e1d6e44b31a9b/bc3a8/aeons-gallery.jpg 800w,\n/static/a6b3bfd7e29dc69b701e1d6e44b31a9b/81ef8/aeons-gallery.jpg 1200w,\n/static/a6b3bfd7e29dc69b701e1d6e44b31a9b/989b1/aeons-gallery.jpg 1600w,\n/static/a6b3bfd7e29dc69b701e1d6e44b31a9b/68945/aeons-gallery.jpg 1639w","srcWebp":"/static/a6b3bfd7e29dc69b701e1d6e44b31a9b/c6096/aeons-gallery.webp","srcSetWebp":"/static/a6b3bfd7e29dc69b701e1d6e44b31a9b/1932c/aeons-gallery.webp 200w,\n/static/a6b3bfd7e29dc69b701e1d6e44b31a9b/f4957/aeons-gallery.webp 400w,\n/static/a6b3bfd7e29dc69b701e1d6e44b31a9b/c6096/aeons-gallery.webp 800w,\n/static/a6b3bfd7e29dc69b701e1d6e44b31a9b/b6424/aeons-gallery.webp 1200w,\n/static/a6b3bfd7e29dc69b701e1d6e44b31a9b/7a72d/aeons-gallery.webp 1600w,\n/static/a6b3bfd7e29dc69b701e1d6e44b31a9b/fd5c5/aeons-gallery.webp 1639w","sizes":"(max-width: 800px) 100vw, 800px","originalImg":"/static/a6b3bfd7e29dc69b701e1d6e44b31a9b/68945/aeons-gallery.jpg","originalName":"aeons-gallery.jpg","presentationWidth":800,"presentationHeight":541}}},"url":"/building-aeons/","categories":["Programming","web3","Crypto","Software Engineering","NFTs"],"tags":["Solidity","TypeScript","Next.js","Base","Ethereum","Ponder","NFTs","Onchain Art","Full Stack"],"hide_featured_image":true,"mentorship_og_image":null,"custom_excerpt":"How to build a full stack onchain art project on Base. This article shows the smart contract suite, the blockchain indexer and other services that add to the experience."}}},"pageContext":{"slug":"/building-aeons/","previous":{"fields":{"slug":"/2024-a-year-in-review/"},"frontmatter":{"title":"2024 - A Year In Review"}},"next":{"fields":{"slug":"/2025-a-year-in-review/"},"frontmatter":{"title":"2025 - A Year In Review"}}}},"staticQueryHashes":["1331100284","1975953531","3493025416"]}