01
Overview
RugBmp is a single ERC-721 contract. It holds no images: each token stores the seed it was minted with, and every read regenerates the NAQSHE from it.
- The seed draws the raj, how finely the NAQSHE is knotted, which sets its grid: 40 × 56, 50 × 70, 60 × 84 or 80 × 112 knots.
- It is hashed into a stream of random draws that fix every other parameter of the NAQSHE.
- The parameters are drawn into a quarter, one palette index per knot (30 × 42 at Town).
- The quarter is mirrored left to right and top to bottom into the whole NAQSHE.
- The knots are written as an uncompressed 8-bit BMP, then wrapped in JSON as a data URI.
The source is three Solidity files that compile into that one contract: RugBMP.sol, the ERC-721 and the mint; NAQSHE.sol, the art; and Utilities.sol, strings and base64. MIT licensed and verified on the explorer.
02
Contracts
| Network | Address |
|---|---|
| Ethereum | not deployed yet |
| Sepolia | 0x6F5E3b35ac49203bee82848E1C5E6611B73a66c9 |
Functions
| Function | What it does |
|---|---|
| createNAQSHE(seed, drawing) payable | Draw the NAQSHE for seed and mint it to you. Send exactly price. With drawing, the mint also writes the NAQSHE as ASCII text in a Drawn event; without it the mint costs far less gas. Returns the new id. |
| draw(id) | For a NAQSHE minted without its drawing: its collector writes the drawing on chain. Once only; after that it is locked, for every later collector too. |
| drawnAt(id) | The block the drawing was written in, or 0 if it has not been drawn yet. |
| previewBMP(seed, scale) | The raw BMP for any seed, minted or not. scale 1–16 pixels per knot. Free to call. |
| traits(seed) | The metadata attributes for any seed, as a JSON array string. Cheap enough to call per keystroke. |
| tokenURI(id) | The full metadata as data:application/json;base64,… with the BMP inline, at imageScale fitted to the raj: every NAQSHE about 240 pixels wide. |
| seedOf(id) | The seed token id was minted with. Never changes. |
| seedTaken(seed) | Whether a seed has already been minted. |
| totalSupply() | NAQSHE minted so far. Ids run from 0 to totalSupply − 1; at most 64. |
| price() | The mint price in wei, fixed at deploy. |
| CreateState() | Whether minting is open. Closed from deploy until the owner opens it; the owner can close and reopen it. |
| imageScale() | Pixels per knot in tokenURI images at Town, fixed at deploy. Other rajes scale to about the same width: ⌈imageScale × 6 / t⌉, t = knots across ÷ 10. |
| ownerOf, balanceOf, transferFrom, safeTransferFrom, approve, setApprovalForAll | Standard ERC-721. |
| createNAQSHEforArtist(seed, drawing) | Owner only: the artist's mint, free and to the owner, whether minting is open or closed. It counts toward the 64 and takes its seed like any other. |
| setState(open), withdraw() | Owner only: open or close minting, collect the mint proceeds. |
03
Minting
Anyone can mint until 64 NAQSHE exist. You choose the seed: any whole number from 0 up to 2²⁵⁶ − 1. Preview it first; if nobody has minted it, mint it at the current price. Ids are given out in order, starting at 0.
- Pick a seed on the minter, or roll one. The preview and traits update as you type.
- Connect a wallet on the right network. The minter offers to switch if you are elsewhere.
- Confirm the transaction. When it lands, your NAQSHE appears in the gallery with your mark on it.
Because each seed can be minted only once, two people racing for the same seed cannot both win: the second transaction reverts with SeedTaken and costs only gas.
04
From seed to NAQSHE
The seed is hashed as keccak256(abi.encode(seed, "RugBMP")) and every random draw is read from that hash in a fixed order. The order is part of the format: it is why a seed always gives the same NAQSHE.
Raj
Before anything else the seed draws its raj from a hash of its own, keccak256(abi.encode(seed, "Raj")): Nomad (raj 20) 3 times in 20, Village (25) 5, Town (30) 8 and Workshop (40) 4. Raj 5t gives a grid of 10t knots across by 14t long, always the sample's 5 : 7. Everything below is measured in t, and at Town it is exactly the first RugBmp's generator.
Borders
Built from the outer edge inwards: an optional thin edge stripe, the outer band, one or two guard stripes, the main band, one or two more stripes, and sometimes a 3-row minor band, keeping the whole border within 3t rows. The bands widen with the raj: at Town the outer band is 4–6 rows and the main band 5 or 7, within 18; Nomad keeps to single guard stripes. Each band takes a motif and a background; guard stripes are solid yarns, never the ground and never the same as the stripe before.
Field
The field is open, holds a small repeat (diamonds, dots or saltires) at an odd pitch so the mirror seam vanishes, or is drawn: value noise, thresholded into blobs and outlined in the dark yarn, like a hand-painted field. A medallion and corner pieces may be layered on top.
At Town (1 in 4) and Workshop (3 in 5) the field may instead be one of six fine designs, drawn from a stream of their own so the rest of the NAQSHE is unchanged: Herati, a rosette in a diamond with a leaf on each side; Mina Khani, daisies on a diamond trellis; Joshaghan, a lattice of stepped shrubs; Kheshti, garden panels of cypresses and flowering trees; rows of boteh; or Moharramat stripes. Each cell is symmetric about its middle, so the mirror seams stay invisible. Kheshti and Moharramat fill the field alone; the others take the medallion and corners.
Mirror
The quarter is the top-left corner of the NAQSHE. Column x of the NAQSHE reads quarter column x or W − 1 − x, and row y reads y or H − 1 − y, so the NAQSHE is symmetrical about both centre lines.
05
Traits
Every trait is a parameter drawn from the seed, so traits(seed) can report them without drawing a single knot.
| Trait | Values | Meaning |
|---|---|---|
| Knots | 40 x 56, 50 x 70, 60 x 84, 80 x 112 | The whole NAQSHE's grid, across by long, set by how finely it is knotted (its raj: 20, 25, 30 or 40), drawn from the seed: 3, 5, 8 and 4 in 20. 60 x 84 is the sample NAQSHE's own. |
| Palette | Madder, Indigo, Saffron, Kermes, Celadon, Charcoal | The eight yarns. Madder, the sample's own, is drawn six times in sixteen; the rest twice each. |
| Layout | Framed, Banded | Framed: the border runs round all four sides with mitred corners. Banded: borders run across the ends only. |
| Outer border | Lattice, Lozenge, Chevron, Reciprocal, Rosette, Vine, Cartouche, Star, Scatter, Sampler | The motif of the outermost band. Sampler is a band of mixed panels. |
| Main border | as above | The motif of the main, widest band. |
| Medallion | None, Diamond, Stepped, Octagon, Square | The shape at the centre of the field. |
| Corners | Yes, No | Whether quarter medallions sit in the corners of the field. |
| Field | Open, Diamond repeat, Dot repeat, Saltire repeat, Drawn, Herati, Mina Khani, Joshaghan, Kheshti garden, Boteh, Moharramat | Drawn is a freehand, outlined field like the sample's hand-painted centre. The last six are the fine designs, for Town (1 in 4) and Workshop (3 in 5) only; Kheshti and Moharramat take no medallion or corners. |
| Abrash | Yes, No | Whether stripe colours drift into a neighbouring shade, as with yarn from a new dye batch. |
| Border depth | number | How many knot rows the border takes, from the edge to the field: at most 12, 15, 18 or 24 by raj. |
06
The BMP format
Each NAQSHE is a standard Windows bitmap: a 14-byte file header, a 40-byte info header, an 8-colour palette, then the pixels. Rows pad to a multiple of 4 bytes; only Village's 50 knots across needs it. At scale 1 the file is 2,326 (Nomad), 3,726 (Village), 5,126 (Town) or 9,046 bytes (Workshop).
| Offset | Bytes | Field |
|---|---|---|
| 0 | 2 | BM |
| 2 | 4 | File size: 86 + row × 14t·s, row = 10t·s rounded up to a multiple of 4 |
| 10 | 4 | Pixel offset: 86 |
| 14 | 4 | Info header size: 40 |
| 18 | 4 | Width: 10t × scale (40, 50, 60 or 80 knots across) |
| 22 | 4 | Height: 14t × scale, positive, so rows run bottom-up |
| 26 | 2 | Planes: 1 |
| 28 | 2 | Bits per pixel: 8 |
| 30 | 4 | Compression: 0 (none) |
| 34 | 4 | Image size in bytes |
| 38 | 8 | Resolution: 2835 px/m (72 DPI) both ways |
| 46 | 8 | Colours used and important: 8 |
| 54 | 32 | Palette: 8 entries of blue, green, red, 0 |
| 86 | … | Pixels: one palette index per byte, bottom row first, each row padded with zeros to 4 bytes |
The palette is always laid out in the same slots: ground, gold, red, teal, pink, dark, navy, maroon. Slot 5 is always the outline yarn. Each palette fills the slots with its own colours.
07
Token metadata
tokenURI(id) returns base64 JSON, and inside it the BMP, also base64. Decoded, it looks like this:
{
"name": "NAQSHE #7",
"description": "",
"image": "data:image/bmp;base64,Qk0W…",
"attributes": [
{ "trait_type": "Knots", "value": "80 x 112" },
{ "trait_type": "Palette", "value": "Madder" },
{ "trait_type": "Layout", "value": "Framed" },
…
{ "trait_type": "Border depth", "value": 14 }
]
}At imageScale 4 every NAQSHE comes out 240 or 250 pixels wide, the tokenURI is 141–154 KB, and it costs 17–23M gas to compute depending on the raj, within the usual 50M eth_call limit. A mint costs 2–9M gas, well under the 16.78M one transaction may use. For apps, previewBMP(seed, 1) and traits(seed) are far lighter.
08
Reading from code
With viem, any NAQSHE in a few lines. No wallet needed; any RPC will do.
import { createPublicClient, http, parseAbi, hexToBytes } from "viem"
import { sepolia } from "viem/chains"
const contract = "0x6F5E3b35ac49203bee82848E1C5E6611B73a66c9"
const abi = parseAbi([
"function seedOf(uint256 id) view returns (uint256)",
"function previewBMP(uint256 seed, uint256 scale) pure returns (bytes)",
"function traits(uint256 seed) pure returns (string)",
])
const client = createPublicClient({ chain: sepolia, transport: http() })
const seed = await client.readContract({ address: contract, abi, functionName: "seedOf", args: [0n] })
const bmp = await client.readContract({ address: contract, abi, functionName: "previewBMP", args: [seed, 1n] })
const traits = JSON.parse(await client.readContract({ address: contract, abi, functionName: "traits", args: [seed] }))
// bmp is the whole file: save it, or show it
const bytes = hexToBytes(bmp)
const src = URL.createObjectURL(new Blob([bytes], { type: "image/bmp" }))Reading a knot straight from the bytes, without decoding the image:
// x from the left, y from the top, at scale 1
function knot(bytes: Uint8Array, x: number, y: number) {
const index = bytes[86 + (59 - y) * 84 + x] // rows are stored bottom-up
const p = 54 + index * 4 // palette entries are B, G, R, 0
return { index, rgb: [bytes[p + 2], bytes[p + 1], bytes[p]] }
}When you show a NAQSHE, scale it without smoothing so each knot stays square:
img.naqshe { image-rendering: pixelated; }09
Views on this site
Every NAQSHE page offers four views. All of them start from the same bytes the contract returns.
- BMP: the bitmap itself, enlarged knot for knot. This is the token's image.
- NAQSHE ¼: the quarter on a chart (30 × 42 at Town), as a designer would hand it to the weaver, with a key of knots per yarn.
- NAQSHE full: all four quarters on one sheet, the heavy lines counted from the centre.
- Rug: a render in the browser of the NAQSHE as wool on a floor, at 22 × 22 knots per square of grid, with pile height, sheen, iron-corroded darks, abrash, fringe and selvedges, under studio, raking or window light, on a light or dark floor.
Each view downloads: the BMP as it is, the others as large PNGs, and the two NAQSHE sheets also as SVG.
10
FAQ
- How do I get in touch?
- Write to naqshe@beinol.xyz: questions, licence requests for commercial use, press, or a notice that something here infringes your rights.
- Where is the image stored?
- Nowhere. The contract stores one number per NAQSHE, its seed, and draws the bitmap again whenever anyone asks. The same seed always gives the same bytes.
- Can I preview a NAQSHE before minting?
- Yes. previewBMP and traits work for any seed, minted or not, and cost nothing to call. The minter on the home page calls them as you type.
- Why BMP and not SVG or PNG?
- A BMP needs no compression and no markup: a fixed header, a palette, then one byte per knot. That makes it cheap and simple to write from a contract, and every browser and marketplace can show it.
- Why does my wallet show my NAQSHE blurred?
- Some wallets smooth small images when they enlarge them. tokenURI images are written several pixels per knot to stay crisp, about 240 pixels wide at every raj; this site always shows the raw grid scaled with nearest-neighbour.
- Why are some NAQSHE blockier than others?
- Each is knotted at its own raj, drawn from the seed. A Nomad NAQSHE has 40 × 56 knots and a Workshop one 80 × 112, but wallets show them at about the same size, so the coarse weaves have bigger knots, as coarse rugs do.
- Can the contract or the art change?
- No. There is no upgrade path, and a NAQSHE depends only on the seed. The owner can open and close minting, mint free as the artist within the 64, and collect proceeds, nothing else. The price is fixed at deploy.
- Are the Rug and NAQSHE views on-chain?
- Their input is: they are drawn in your browser from the exact bytes the contract returns. The rendering itself is this site's, so the official image is the BMP.
More on the craft behind the format is in the library.