Skip to content

Inscription Memos#4504

Open
bruffstar wants to merge 1 commit intoordinals:masterfrom
bruffstar:feat/memo-tag-27
Open

Inscription Memos#4504
bruffstar wants to merge 1 commit intoordinals:masterfrom
bruffstar:feat/memo-tag-27

Conversation

@bruffstar
Copy link
Copy Markdown

Summary

Memos allow the owner of an inscription to attach append-only data to it. A memo is itself an inscription, linked to its target via tag 27, and burned on creation by sending it to an OP_RETURN output. Because the memo is burned, it does not remain in the creator's wallet. The content remains permanently readable on-chain.

This is useful for applications that need to update state over time without reinscribing or creating child inscriptions - things like settings, game moves, provenance records, voting, and versioned app updates.

How it works

  • The target inscription must be spent as an input (same mechanic as parent-child)
  • Tag 27 contains the serialized binary inscription ID of the target
  • The memo inscription is sent to an OP_RETURN output with 1 sat
  • The memo receives the "burned" charm
  • Tag 27 is odd, so memos are backwards compatible with older versions of ord

Changes

Tag & envelope parsing

  • New Tag::Memo = 27 with chunked value support
  • Inscription::memo_target() decodes the tag to InscriptionId

Indexing

  • New SEQUENCE_NUMBER_TO_MEMOS multimap table (schema version 34 -> 35)
  • Paginated memo lookup and indexed access (including negative indexing from the end)

Recursive endpoints

  • GET /r/memos/<ID> - first page of memo IDs
  • GET /r/memos/<ID>/<PAGE> - paginated memo IDs
  • GET /r/memos/<ID>/at/<INDEX> - memo at index (supports negative)
  • GET /r/memos/<ID>/at/<INDEX>/content - memo content at index (proxied)

Wallet CLI

  • ord wallet inscribe --memo <INSCRIPTION_ID> --file <FILE>
  • --memo cannot be combined with --parent (an inscription is either a memo or a child, not both)
  • Supports --delegate for content-free memos that resolve through a delegate

Explorer

  • Inscription detail page shows "memo for" link on memo inscriptions pointing to the target
  • Inscription detail page shows "memos" count with link to /memos/<ID> on target inscriptions (e.g. all (2))
  • Paginated /memos/<ID> browser page listing all memos for an inscription

Docs

  • New inscriptions/memos.md covering spec, CLI, delegate memos, and notes
  • Four new endpoint entries in inscriptions/recursion.md

Tests

  • memo_is_parsed_correctly - tag 27 parsed to correct inscription ID
  • memo_is_parsed_correctly_from_chunks - chunked tag 27 works
  • memos_recursive_endpoint - /r/memos/<ID> returns correct memo list with pagination
  • memo_at_index - /r/memos/<ID>/at/<INDEX> with positive and negative indexing
  • memo_at_index_content - /r/memos/<ID>/at/<INDEX>/content serves memo content
  • memo_without_valid_target_is_ignored - memo with unspent target is not indexed
  • memo_content_readable_after_burn - burned memo content is still accessible
  • memo_proxy - memo content proxied correctly through recursive endpoint

Note

This PR was created with assistance from Claude Code. Please review thoroughly.

@theartistPP

This comment was marked as spam.

@craig-PP

This comment was marked as spam.

@dcorral

This comment was marked as spam.

@casey
Copy link
Copy Markdown
Collaborator

casey commented Mar 27, 2026

Good feature!

(I appreciate the +1s, but they don't make me any more likely to merge something. In fact, they're kind of annoying, so they make me less likely to review and merge something 😅 I've marked them as spam. Please use the react button instead.)

I like this a lot, and I think it's a great feature. Some minor thoughts:

  • The next unused odd tag is 21, so we should use that.
  • I think I would use the word "note" instead of "memo". I just find note to be the most neutral word for that, whereas a "memo" has other connotations.

My biggest piece of feedback is that I think it's confusing and duplicative for memos to be so close to parent-child but not use parent-child. They have so many similarities, namely that the transaction that inscribes a memo must also include the target as an input, and that the memo has a field which encodes the transaction ID of the target in the same way as the parent field encodes the transaction ID of the parent. Also, the current design permits the quite odd scenario of an inscription which is simultaneously a memo and a child.

I think the same end result could be achieved in a simpler way if we added a new odd tag which was "flags" field. The first defined flag would be a single bit which, if set, would not include the inscription in lists of children for any of its its parents, for any purpose, we would also add the ability to query hidden children only, and also the ability to burn inscriptions immediately on creation.

This would have the additional advantage of breaking the feature into three separate smaller PRs, each of which is independently useful, and which are easier to review and merge.

The three PRs would be:

  • Add a --hidden field to ord wallet inscribe and a hidden: true field to ord wallet batch batchfile. If an inscription had the hide field set, the "hidden" flag would be set and it would not be included in lists of children for its parents. We might also hide it in other places, like on the front page.

  • Add the ability to query hidden children only, as a recursive API endpoint and as a web endpoint.

  • Add the ability to burn inscriptions when creating them, with a --burn flag and burn: true field.

We could still call hidden child inscriptions "notes" or "memos", and the UX would be, I think for all practical purposes the same, just decomposed into three complementary features which are independently useful.

@bruffstar
Copy link
Copy Markdown
Author

@casey thanks for the feedback! I actually wanted to call the feature Notes, but noticed there is Note = 15 in the codebase and wasn't sure what it was going to be used for.

Those 3 separate PR ideas are great and should get us exactly what we need!

I will give it a go, see if I can reproduce this setup locally.

@casey
Copy link
Copy Markdown
Collaborator

casey commented Mar 27, 2026

@bruffstar Oh yeah, the note field. I think that can be ignored. The note tag is there to attach data to an inscription that is guaranteed to be ignored by ord. It's not user-facing, so I think it's fine to recycle the name.

Also, I just had the thought that we might not even need to add the ability to burn inscriptions on creation. We could just give them field 66, which guarantees that they are unbound, so they are never associated with any UTXO and thus don't clutter the wallet. This is much simpler than having to burn them on creation, since we don't have to create a new output and make sure they land in that new output, they just get a new tag.

I would start with just the first PR, the --hidden flag and hidden: true field for inscribing, which sets a bit on a new flags field using tag 21. It should use the least significant bit of the first byte of the field.

@gmart7t2
Copy link
Copy Markdown
Contributor

If an inscription had the hide field set, the "hidden" flag would be set and it would not be included in lists of children for its parents

Would the child still list its parents? That is, is the hiding only in the parent -> child direction, but not in the opposite direction?

Maybe there's an extra bit to say whether the hiding happens in both directions

@lifofifoX
Copy link
Copy Markdown
Collaborator

I would like to push back on the idea of "hidden" children. Something like that really blurs the line between provenance concern and UI/UX concern. These things tend to then get muddy over time.

IMO, a better implementation for something like this would be along the lines of #3025 or #4268. Entirely separate from parent/child. Additionally, it could be more open and let anyone send memos to any inscription. But then add the ability to specifically query ones that carry some provenance.

@bruffstar
Copy link
Copy Markdown
Author

I would really worry about spam if it was open to anyone. The idea at the start was only similar to parent child in that we include the target as an input, but feel this is now skewing away from what we want to achieve, which is appending contextual data over time to owned inscriptions. To avoid any confusion with parent/child, then it goes back to my original approach above which seperated concerns as "memos" (or notes, whatever we call them).

@lifofifoX
Copy link
Copy Markdown
Collaborator

I would not worry about spam at all. This is no different than inscribing into someone else's wallet. There's a real cost involved. Just that the UI should not be exposing it on inscription page.

Underlying concept here is being able to attach inscriptions/data to existing inscriptions and be able to query them. Provenance layer is a subset of it.

@bruffstar
Copy link
Copy Markdown
Author

bruffstar commented Mar 28, 2026

I would prefer Casey's hidden children approach for its simplicity, but I think reusing parent/child for this may cause confusion down the line. Keeping it as a separate tag feels cleaner, which is what this PR already does.

But I disagree on open access. The use case isn't "attach data to someone else's inscription", it's an owner appending data to their own inscription over time. Settings, game state, provenance records, votes. The inscription reads its own notes and renders from them. If anyone can write, the target can't trust the data at its own endpoints without verifying provenance on every read. With owner only, it handles that at the protocol level.

I think I came up with a pretty elegant and unobtrusive way of displaying these in the ord explorer as well if you check out the demos. Recursive endpoints follow same pattern as r/sat/ as well.

It's also not comparable to inscribing into someone's wallet. Anything touching the wallet is exactly what we're trying to avoid here. Notes are burned, no UTXO, no wallet impact, that's the whole point.

I'd propose keeping it as a dedicated tag, separate from parent/child, owner-only. Happy to adjust the tag number and naming per Casey's feedback.

@casey
Copy link
Copy Markdown
Collaborator

casey commented Mar 28, 2026

IMO, a better implementation for something like this would be along the lines of #3025 or #4268. Entirely separate from parent/child. Additionally, it could be more open and let anyone send memos to any inscription. But then add the ability to specifically query ones that carry some provenance.

I think this value of this feature is specifically that it's gated to the inscription owner, offerings are open to anyone.

I would like to push back on the idea of "hidden" children. Something like that really blurs the line between provenance concern and UI/UX concern. These things tend to then get muddy over time.

I'm definitely sympathetic to this line of thinking, but I can't really think of any concrete problems that hidden children would have, and it makes the feature much simpler.

@lifofifoX
Copy link
Copy Markdown
Collaborator

I'm definitely sympathetic to this line of thinking, but I can't really think of any concrete problems that hidden children would have, and it makes the feature much simpler.

Concern is primarily complexity in muddling provenance and UI concerns. "Hidden" is a purely UI thing, and would not carry any meaning outside that context. It's implementation detail (for keeping it simple) leaking into the protocol.

@casey
Copy link
Copy Markdown
Collaborator

casey commented Mar 28, 2026

I don't actually think it's muddling them. Basically, people want to be able to create child inscriptions (can only be created by inscription holder, are inscriptions that can carry data in all the ways that inscriptions can carry data), but they want to do so to modify the original inscription through its recursive queries, and don't want the child to appear in the list of child inscrtipions.

So a note is an inscription and does have provenance, because the holder created it, but shouldn't show up in the list of children. So the minimal implementation of this feature is a hidden flag.

@lifofifoX
Copy link
Copy Markdown
Collaborator

In that case, I think @bruffstar's original implementation of using a separate tag seems conceptually much cleaner/simpler. Because memo is more explicit about purpose/use case. You can then have a memo that's also optionally a child.

Something like a hidden child flag comes with its own set of ambiguities. How should marketplaces and wallets handle it? Should they show up in wallets? Be tradeable? Would that choice match the user expectations?

I'll end my bikeshedding with that!

@casey
Copy link
Copy Markdown
Collaborator

casey commented Mar 28, 2026

These are good questions:

Something like a hidden child flag comes with its own set of ambiguities. How should marketplaces and wallets handle it? Should they show up in wallets? Be tradeable? Would that choice match the user expectations?

They should appear as inscriptions, and there should be a back-link to the parent, but they should not appear in the parents list of children, or appear when searching for children of a given parent.

They should appear in wallets, and they should be tradable, since they're inscriptions.

@bruffstar
Copy link
Copy Markdown
Author

They should appear in wallets, and they should be tradable, since they're inscriptions.

I think if "notes" appear in wallets and are tradable, they are just children with a display filter and that's exactly the wallet pollution problem this feature is trying to solve. If I send 100 game moves as notes, I don't want 100 inscriptions sitting in my wallet. The owner shouldn't have to manage, ignore, or want to sell them.

The idea would be fire and forget, the data lives on-chain, the inscription reads it, and the owner's wallet stays clean.

In this PR they burn to OP_RETURN (and show burn charm, which I also like). Tag 66 idea would also work, unbound, but may be a bit more confusing. Either way, notes not being in the wallet is what makes this useful.

@casey
Copy link
Copy Markdown
Collaborator

casey commented Mar 28, 2026

What I mean by "they should be tradable" is that if they're in your wallet, you can see them and trade them. This can be avoided by burning them or tagging them with tag 66, which makes them untradable. This is orthogonal to whether or not they are child inscriptions or not.

@bruffstar
Copy link
Copy Markdown
Author

Agreed, the parent/child question and the burn question are separate. My concern with the hidden children route is that it layers multiple features to get one behavior. A hidden flag, plus tag 66 or burn, plus filtered children endpoints.

Each piece isn't really useful on its own (except burn feature), a hidden child that isn't burned is just a child with a broken UI. They kind of only make sense combined.

A dedicated tag gives you the "Note" identity, the indexing, and the endpoints in one concept. The recursive endpoints are also very clean... /r/notes/<ID> instead of /r/children/<ID>/hidden.

I wanted to use parent/child when I was trying this, but I landed on this setup. Notes use the same ownership mechanic as parent/child, spending the target as an input. But that's where the similarity should end. Same proof but with different purpose and understandable endpoints.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants