Every Chonk has an Ethereum address associated with it. We call it your Chonk's Backpack.

It holds your Traits, any collectibles, tokens, or coins the Chonk wants to carry. Occasionally, collectors are rewarded for what's in their Chonk's Backpack, so sending things to it should be as easy as sending tokens to any other address.

Introducing ChonkNames

Before, your Chonk's Backpack address looked like this: 0xcb16004F6E10820Ba6314310334E2E72A701c8BA

Now, with ChonkNames, it looks like this: 1.chonks.base.eth

It's easy to remember as long as you know your Chonk's ID. It's live now for every Chonk.

Here's how we built it

Our deployer wallet owns chonks.base.eth, so we needed to create subnodes. That required us to call the Basenames Registry contract and setSubnodeRecords.

Then we called the L2Resolver contract with keccak256(abi.encodePacked(namehash, subnode));, creating the new node for your particular ID such as 6551.chonks.base.eth.

We used the multicall function to register batches of these – once for each Chonk.

One small note: The Chonks Deployer address technically owns the subdomain for your Chonk's Backpack so there's no additional functionality other than a nice alias for your Backpack.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import "forge-std/Script.sol";
import "forge-std/console.sol";
import { LibString } from "solady/utils/LibString.sol";

interface IBasenamesRegistry {
    function setSubnodeRecord(bytes32 node, bytes32 label, address owner, address resolver, uint64 ttl) external;
}

interface L2Resolver {
    function setAddr(bytes32 node, address addr) external;
    function addr(bytes32 node) external view returns (address);
    function multicall(bytes[] calldata data) external returns (bytes[] memory results);
    function setApprovalForAll(address operator, bool approved) external;
    function approve(bytes32 tokenId, address operator, bool approved) external;
}

interface IChonksMain {
    function tokenIdToTBAAccountAddress(uint256 tokenId) external view returns (address);
}

contract ChonkNamesScript is Script {
    using LibString for uint256;

    IChonksMain public chonksMain = IChonksMain(0x07152bfde079b5319e5308C43fB1Dbc9C76cb4F9);
    uint256 constant BATCH_SIZE = 50;
    uint256 constant TOTAL_RECORDS = 83300;

    address constant owner = 0xA1454995CcCC837FaC7Ef1D91A1544730c79B306; // Chonks Deployer
    // namehashed chonks.base.eth using viem (https://viem.sh/docs/ens/utilities/namehash.html)
    bytes32 constant namehash = 0xf1834f255e2413aaa4b50ea150c0e87344384ef6fcea37f072ad16e77d43414d;
    IBasenamesRegistry constant registry = IBasenamesRegistry(0xB94704422c2a1E396835A571837Aa5AE53285a95);
    address constant resolver = 0xC6d566A56A1aFf6508b41f6c90ff131615583BCD;
    L2Resolver constant resolverContract = L2Resolver(resolver);

    function run() external {}

    // Process a specific range of IDs
    function processBatch(uint256 startId, uint256 endId) public {
        require(startId > 0 && endId <= TOTAL_RECORDS, "Invalid ID range");
        require(startId <= endId, "Start ID must be <= end ID");

        console.log("Processing batch from ID %d to %d", startId, endId);

        // First create all subnodes in this batch
        for (uint256 i = startId; i <= endId; ++i) {
            // encode and hash the token id as a string
            bytes32 subnode = keccak256(abi.encodePacked(LibString.toString(i)));
            registry.setSubnodeRecord(namehash, subnode, owner, resolver, 0);
            console.log("Created subnode for ID %d", i);
        }

        // Then process setAddr calls in smaller batches
        for (uint256 batchStart = startId; batchStart <= endId; batchStart += BATCH_SIZE) {
            uint256 batchEnd = batchStart + BATCH_SIZE - 1;
            if (batchEnd > endId) batchEnd = endId;

            uint256 batchSize = batchEnd - batchStart + 1;
            bytes[] memory calls = new bytes[](batchSize);

            for (uint256 i; i < batchSize; ++i) {
                uint256 tokenId = batchStart + i;
                bytes32 subnode = keccak256(abi.encodePacked(LibString.toString(tokenId)));
                bytes32 node = keccak256(abi.encodePacked(namehash, subnode));
                address tbaAddress = chonksMain.tokenIdToTBAAccountAddress(tokenId);

                calls[i] = abi.encodeWithSelector(
                    L2Resolver.setAddr.selector,
                    node,
                    tbaAddress
                );
            }

            // Execute batch of setAddr calls
            resolverContract.multicall(calls);
            console.log("Processed addresses %d to %d", batchStart, batchEnd);
        }
    }

}

Thank you to Jesse and Steve Katzman at Base for their help on this project.