Protocol specification · draft v0 · 2026-09-23

Burning Take protocol

A way to vote on Bitcoin by burning sats. No token, no contract, no server, no owner. Everything below can be implemented from this page alone, and every rule is checkable against the chain.

TermsAddress derivationName grammarVote transactionReceiptsStatementsCountingSnapshotsRegistrationSponsoringReference codeVersioningSecurity notes

1 · Terms

TermMeaning
TopicA question or topic. Identified by a name, addressed by the P2WSH address derived from that name. Examples: general, paris-cars?yes|no, btc-eoy-2026?50000..500000@966500.
StatementThe text a voter burns for. Carried in an OP_RETURN output. In a closed question it is one of the options, in a numeric question it is a number.
TakeWhat the Burning Take app calls a statement. Other front ends may use any word.
BurnA transaction output paying the topic address. Its value is the vote's weight. It can never be spent by anyone.
Root topicThe topic with the empty name. Its statements are topic names. Burning to it registers a topic for discovery.
TipAn optional output to a spendable address chosen by whoever runs a front end. Not part of the vote. Never mixed with the burn.

2 · Address derivation

A topic address is a pay-to-witness-script-hash (BIP 141) address of a script that can never succeed.

Canonical name

Apply the grammar in section 3 to get the canonical spelling, then take its UTF-8 bytes.

Witness script

OP_RETURN (0x6a) followed by a minimal push of the name bytes: a single length byte for 1 to 75 bytes, OP_PUSHDATA1 (0x4c) plus a length byte for 76 to 255 bytes. The empty name gives the one-byte script 6a.

Hash

Single SHA-256 of the script bytes.

Address

Witness version 0 with the 32-byte hash as program, encoded as bech32 with human-readable part bc on mainnet, tb on signet and testnet.

Root topic address · mainnet
bc1qrz05qq6tu7senu06nzgkdrhr4dsyn7pd8rrghec0t9h2ktsc27msqvznj2

Script 6a, SHA-256 189f40034be7a199f1fa9891668ee3ab6049f82d38c68be70f596eab2e1857b7. Any implementation must reproduce this address exactly.

Why it is unspendable. Spending a P2WSH output requires executing the witness script successfully. OP_RETURN fails the script unconditionally under consensus rules, so no witness exists that can spend the output. See the verification page for how to check this on your own node.

Consequence. Because the output is never spent, the witness script, and therefore the name, is never revealed on chain. The only on-chain record of a topic's name is a registration burn on the root topic. An unregistered topic exists and can be counted by anyone who knows its name, and by nobody else.

3 · Name grammar

name     = question [ "?" answers ] [ "@" deadline ] [ "!" minimum ]
answers  = option "|" option *( "|" option )      ; two or more distinct options
         / number ".." number                       ; a numeric range, low..high
deadline = 1*DIGIT                                  ; block height
minimum  = 1*DIGIT                                  ; sats per burn
question = 1*( lowercase / DIGIT / "-" / "/" / other-unicode )   ; never ? | @ ! or whitespace

Normalisation, applied before derivation

PartRule
whole nameLowercase. Trim. Modifiers are re-emitted in canonical order: answers, then deadline, then minimum. q?a|b!1000@966000 and q?a|b@966000!1000 are the same topic and derive the same address.
questionWhitespace runs become a single -. The characters ? | @ ! are removed. / is kept and means a folder: paris/cars is an ordinary name whose address differs from paris. Nothing aggregates across folders.
optionsEach option is normalised like a statement (section 5). Duplicates are dropped. Fewer than two options after that means the name has no answers part.
rangeTwo decimal numbers, low first. Reversed bounds are swapped. Integers are written without a decimal point.
deadline, minimumDecimal integers with no leading zeros. A zero minimum is omitted.

Kinds of topic

NameKindFront-end behaviour
generalopenFree-text statements, ranked by burn.
paris-cars?yes|noduelExactly two options. Shown as a two-sided bar.
editor?vim|emacs|vscodepollThree or more options, one row each, always shown even at zero.
fee?0..1000numberStatements are numbers in the range. Burn-weighted mean and median, histogram.
vote?yes|no@966000with deadlineBurns confirmed at height 966000 or later are late and not counted.
vote?yes|no!5000with minimumBurns under 5000 sats are shown but not counted.

4 · Vote transaction

A vote is any Bitcoin transaction with the following outputs. Inputs are unrestricted, the identity of the voter is whatever the inputs reveal, and nothing else about the transaction matters.

OutputRequiredContent
burnyesPays the topic address. Value at least the dust limit, 330 sats for P2WSH, and at least the topic minimum if one is set. This value is the weight.
statementnoAn OP_RETURN output with the statement as UTF-8, at most 80 bytes, value 0. One per transaction. A vote without it is an abstain: it adds weight to the topic and to no statement.
tip, changenoAnything else. Ignored by counting.

A transaction with an OP_RETURN but no output to the topic address is not a vote and is invisible to the protocol. Several burn outputs to the same topic in one transaction are summed. A transaction that pays two different topic addresses is two votes, each with its own weight and the same statement, unless the statements are batched as below.

Several burns in one transaction

One transaction can carry votes on several topics, each with its own statement. Take the OP_RETURN outputs in order, split each payload on the byte 0x1F (unit separator), and assign the pieces in order to the outputs that pay topic addresses, in output order. Extra pieces are ignored. Topic outputs without a piece, or with an empty piece, are abstains.

A builder puts the statements joined by 0x1F into one shared OP_RETURN when they fit in 80 bytes: one shared OP_RETURN relays on every node. When they do not fit, it writes one OP_RETURN per topic output, right after that output; several OP_RETURN outputs in one transaction need post-Core-30 relay policy and may not propagate everywhere. A single vote is the degenerate case: one topic output, one payload, no separator.

A batch (several burns a front end keeps aside until they are signed together) and a watchlist (topics a browser follows) are client-side conveniences: nothing about them reaches the chain, and nothing in this protocol depends on them.

Receipts

A burn is identified by the txid of its transaction, and by nothing else. There is no receipt object on chain: the transaction is the receipt, and any front end can render it from the txid alone by reading the outputs (burn value, topic address, statement) and the first input's address as the burner. A transaction that burns in several topics is one receipt with several burns, one per topic output, in output order. Anyone can verify a receipt with the recount in the verification page.

5 · Statements

Statements are matched after normalisation: trim, lowercase, collapse whitespace runs to one space, strip trailing . ! ?. The display form is the spelling of the first burn seen. There is no other syntax inside a statement. It is speech.

In a numeric topic the normalised statement is parsed as a decimal number. Suffixes k and m multiply by a thousand and a million. Anything that does not parse, or falls outside the range, is an off-list answer.

6 · Counting

Collect

Every transaction paying the topic address, confirmed or in the mempool. For each: the burn value, the statement if any, the confirmation height if any.

Bucket

In order: late if the topic has a deadline and the burn confirmed at that height or later, or is unconfirmed while the chain tip is one block below the deadline or beyond. Below minimum if the topic has a minimum and the burn is under it. Otherwise counted.

Aggregate

Counted burns are summed per normalised statement. In a closed question, statements that are not options go to other. In a numeric topic, values outside the range go to other. Buckets are always displayed with their totals, never hidden.

Rank

By sats. Vote counts are informational only, since anyone can split a burn into many transactions. A topic's own weight, used to rank topics against each other, is the total ever received by its address.

Numeric topics also report the burn-weighted mean and the weighted median of counted values.

Time windows such as "this week" are 144 blocks per day, applied to the confirmation height. All-time totals need only an address summary, windowed totals need the address history.

Snapshots

A host may publish a snapshot: the finished scan of a topic as a static file (the burns with their txid, statement, value, height and burner, plus the height it was taken at), keyed by the SHA-256 of the canonical name. A snapshot is a cache the host publishes, not a source of truth: a counter that starts from one must still fetch every burn confirmed after its height, and must treat it like any explorer answer, checkable by re-scanning the address from scratch. A snapshot that disagrees with the chain is wrong, the chain is not.

7 · Registration and discovery

A topic becomes discoverable by burning to the root topic with the topic's canonical name as the statement. This is an ordinary vote in an ordinary topic, so registrations are ranked by sats like everything else and can be repeated by anyone. Since the statement is limited to 80 bytes, only names of 80 bytes or fewer can be registered.

Registration grants nothing. It does not create the topic, which exists as soon as its name does, and it does not confer ownership, moderation rights, or exclusivity. Two people can register the same name; that is one topic with two registration burns.

A front end may keep its own list of names in addition to the root topic, and may refuse to display any topic. It cannot prevent anyone else from counting it.

Promotion works the same way as registration, one level up: burning to the open topic named featured with a topic's canonical name as the statement is a vote to sponsor that topic. It is an ordinary topic under ordinary rules: ranked by sats, open to anyone, repeatable, never exclusive. A front end that honours the convention shows the sats burned for a name in featured next to that topic, and may order its front page by them; one that ignores it loses nothing. Nothing about featured is special on chain.

8 · Reference code

Derivation in plain JavaScript, no dependencies. Bech32 per BIP 173.

const CH = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
const polymod = v => { const G=[0x3b6a57b2,0x26508e6d,0x1ea119fa,0x3d4233dd,0x2a1462b3]; let c=1;
  for (const x of v) { const b=c>>>25; c=((c&0x1ffffff)<<5)^x; for (let i=0;i<5;i++) if ((b>>>i)&1) c^=G[i]; } return c>>>0; };
const expand = h => [...h].map(c=>c.charCodeAt(0)>>5).concat(0, [...h].map(c=>c.charCodeAt(0)&31));
const words = bytes => { let acc=0,bits=0,out=[]; for (const b of bytes) { acc=((acc<<8)|b)&0xfff; bits+=8;
  while (bits>=5) { bits-=5; out.push((acc>>bits)&31); } } if (bits) out.push((acc<<(5-bits))&31); return out; };
const bech32 = (hrp, ver, prog) => { const d=[ver,...words(prog)]; const pm=polymod([...expand(hrp),...d,0,0,0,0,0,0])^1;
  const chk=[]; for (let i=0;i<6;i++) chk.push((pm>>>(5*(5-i)))&31); return hrp+"1"+[...d,...chk].map(x=>CH[x]).join(""); };

export async function topicAddress(canonicalName, hrp = "bc") {
  const n = new TextEncoder().encode(canonicalName);
  const script = new Uint8Array(n.length ? [0x6a, ...(n.length <= 75 ? [n.length] : [0x4c, n.length]), ...n] : [0x6a]);
  const hash = new Uint8Array(await crypto.subtle.digest("SHA-256", script));
  return bech32(hrp, 0, hash);
}
// await topicAddress("pizza")  → bc1qzssvapwlcv4um44xqtqay7h04hkq3latjtrxedj7st5d0kv06mhqsxxzg7
// await topicAddress("")       → bc1qrz05qq6tu7senu06nzgkdrhr4dsyn7pd8rrghec0t9h2ktsc27msqvznj2

Cross-check with Bitcoin Core: bitcoin-cli decodescript 6a0570697a7a61 returns the same address under segwit.address.

Test vectors an implementation must pass:

InputCanonical nameScript
Pizzapizza6a0570697a7a61
Paris Cars?yes|noparis-cars?yes|no6a11 + utf8
q?yes|no!1000@966000q?yes|no@966000!10006a14 + utf8
fee?1000..0fee?0..10006a0b + utf8
q?aq6a0171
(empty)(empty)6a

9 · Versioning

Old burns must parse the same way forever, so the grammar only grows. The characters ? | .. @ ! / are reserved. A name that uses a construct this version does not know must be treated literally: the whole string is the question and the topic is open. A future version may give such a construct meaning, and when it does, the address does not change, only the reading of it.

Nothing in this document is enforced by Bitcoin except unspendability. Everything else is a reading convention that front ends agree on. Two front ends that disagree will show different tallies for the same burns, and both are auditable.

10 · Security notes