Version: 2
Prefix: 1PuQa7K62MiKCtssSLKy1kh56WWU7MtUR5
Authors: Satchmo, Attila Aros
Thanks to Unwriter for his input and support on the original specification.
The design goals:
- A simple OP_RETURN protocol for associating data in a single transaction by defining key, value pairs.
- A flexible protocol suitable for many applications
- Compatible with faucets, and other situations where the input address does not represent the author's public identity
- Provide data mapping without enforcing a content, authority or identity scheme (allow other protocols to provide this).
- map a comment to a url
- map an action to a txhash (like, repost, or flag a comment)
- map a photo to a geolocation
- map a 'type' to some data (this is a 'post' or a 'reply')
- map ______ to a _______
- add metadata to a metanet node
- map a metanet node to a node in another tree
In Javascript, localStorage is a familiar and easy to use API. It has methods setItem(key, value) and removeItem(key).
Map has two commands for setting single key value pairs: SET and REMOVE.
A Javascript Set has methods add and delete. Similarly, MAP has ADD and DELETE commands.
The context for these is set by the browser session and current http domain. We can use MAP's SELECT command to specify a transaction id as a context before executing one of these commands. To omit a SELECT is effectively using the transaction itself as the context.
In web programming, your window is the global context. Since Bitcoin is a giant shared context, you need to specify where you are in the graph.
| JS | MAP |
|---|---|
| localStorage.setItem(key, value) | SET key value |
| localStorage.removeItem(key) | REMOVE key |
| let key = new Set(); key.add(value) | ADD key, value |
| key.delete(value) | DELETE key, value |
| localStorage.clear() / Set.clear() | CLEAR txid |
OP_FALSE OP_RETURN 1PuQa7K62MiKCtssSLKy1kh56WWU7MtUR5 <command> ...
Single Value Commands:
Array / List Commands:
Delete:
Command Separator:
These commands are used on a single key/value pair.
SET is the original and most common MAP command. It associates a key with a given value.
SET
<key>
<value>
...Used in conjunction with SELECT. REMOVE is used to remove a previously SET key or all list values at once. Not needed when using metanet protocol.
SELECT
<txid>
REMOVE
<key> ...These commands are used to set and remove members of a list.
Add one or more values to a list.
ADD
<key>
<value> ...Used in conjunction with SELECT. Remove one or more values from a list. (Not needed when using metanet protocol.)
SELECT
<txid>
DELETE
<key>
<value> ...Clear is like deleting every MAP value for a given tx, whether it is an array or a key/value pair. You can repeat txid to clear many txs at once.
CLEAR
<txid> ...When using in conjunction with metanet protocol, it is preferred to instead write a new version of the metanet node to replace the outdated one. This eliminates the need to use REMOVE or DELETE in many cases.
Designates a context by txid. If this is a metanet node, the command applies to that node and its children.
SELECT
<txid>
<cmd (SET | REMOVE | ADD | DELETE)>
?...Note, SELECT should be used only once per instruction set since it sets the context for the subsequent commands.
Use the command separator ::: to declare multiple MAP commands in a single transaction. These commands are considered to be part of the same instruction set.
MAP
SET
key
val
:::
ADD
key2
val
val
val
:::
ADD
key3
val
val
val
:::This evaluates to all three commands in a single output (without repeating the protocol prefix and pipe):
MAP SET key val
MAP ADD key2 val val val
MAP ADD key3 val val val
MAP is designed to be chained together with other OP_RETURN protocols. The input stream flows from the left and can be piped like unix commands. For example, we can select a particular Metanet node, add content from B protocol, and associate attributes such as known global identifiers (txhash, url etc), and finally sign that using the Author Identity protocol:
META | B | MAP | AIP
More about Metanet protocol:
More about B protocol:
More on protocol pipeline:
More on Author Identity Protocol:
Transactions with multiple OP_RETURN outputs are valid on Bitcoin SV. This was not always the case — earlier node software accepted only a single OP_RETURN output per transaction.
This allows the protocol pipelining to have a more specific meaning, and MAP instructions to be spread across multiple outputs when appropriate.
for example, we might have written a transaction that updates the values of multiple previously defined MAP instruction sets:
MAP SELECT <tx1> DELETE key val1 val2 ::: REMOVE key4 | MAP SELECT <tx2> DELETE key val1 val2 ::: REMOVE key4
Since the pipe is used merely as a protocol separator in this example and has no "data flow", we can instead break the instruction sets into multiple outputs:
output1:
MAP SELECT <tx1> DELETE key val1 val2 ::: REMOVE key4
output2:
MAP SELECT <tx2> DELETE key val1 val2 ::: REMOVE key4
In this example we will use SET to comment on a URL with an identity (not using the sender address as the identity's public key). Here is a simple piped OP_RETURN sequence for mapping B data to a global ID, url = https://map.sv.
OP_FALSE OP_RETURN B <data> <content-type> <encoding> | MAP SET 'url' 'https://twitter.com' | AUTHOR_IDENTITY_PROTOCOL <sig_type> <address> <signature>
A more detailed view of the same transaction. This is a comment on markdown formatted B protocol comment on the url 'https://twitter.com'. Each line here is a new pushdata:
OP_FALSE
OP_RETURN
19HxigV4QyBv3tHpQVcUEQyq1pzZVdoAut (B)
"## Hello small world"
text/markdown
utf8
|
1PuQa7K62MiKCtssSLKy1kh56WWU7MtUR5 (MAP)
'SET'
'url'
'https://twitter.com'
|
15PciHG22SNLQJXMoSUaWVi7WSqc7hCfva (AUTHOR_IDENTITY)
'BITCOIN_ECDSA'
<address>
<signature>Keys and values can be repeated to set multiple attributes at once:
MAP
SET
<key>
<val>
<key>
<val>
more detailed example:
1PuQa7K62MiKCtssSLKy1kh56WWU7MtUR5 (MAP)
'SET'
'app'
'metalens'
'type'
'comment'
'url'
'https://twitter.com'
Using a BMAP Planaria you can easily query for records related to this url:
{
"q": {
"find": {
"MAP.app": "metalens",
"MAP.type": "comment",
"MAP.url": "https://twitter.com/"
}
}
}and you'll get a response like this:
{
"c": [{
"i": 0,
"B": {
"content": "## Hello small world",
"content-type": "text/markdown",
"encoding": "utf8"
},
"MAP": {
"cmd": "SET",
"app": "metalens",
"type": "comment",
"url": "https://twitter.com/"
},
"AIP": {
"address": "15PciHG22SNLQJXMoSUaWVi7WSqc7hCfva",
"signature_type": "BITCOIN_ECDSA",
"signature": <signature>
}
}],
"u": []
}This is an example response from a traditional Planaria such as Genesis for the same comment:
{
"c": [{
"i": 0,
"b0": { "op": 106 },
"s1": "19HxigV4QyBv3tHpQVcUEQyq1pzZVdoAut",
"s2": "## Hello small world",
"s3": "text/markdown",
"s4": "utf8",
"s5": "|",
"s6": "1PuQa7K62MiKCtssSLKy1kh56WWU7MtUR5",
"s7": "SET",
"s8": "app",
"s9": "metalens",
"s10": "type",
"s11": "comment",
"s12": "url",
"s13": "https://twitter.com/",
"s14": "|",
"s15": "15PciHG22SNLQJXMoSUaWVi7WSqc7hCfva",
"s16": "BITCOIN_ECDSA",
"s17": "1HQ8momxTp9MYkzDLy9bFMUQgnba189qZE",
"s18": "<signature>"
}],
"u": []
}To remove one of the keys->value mappings from the example above.
1PuQa7K62MiKCtssSLKy1kh56WWU7MtUR5 (MAP)
'SELECT'
<txid>
'REMOVE'
'profile.name'
Since the keyspace is shared, you can either prefix your keys with a unique identifier, or operate in the global space, sharing that dataset and inheriting the emergent schema. Sharing the global namespace can be useful when it is intended to be shared among many apps.
Potential Namespaces for Global Identifiers
url
tx
ethtx
btctx
topic
upc
infohash
ifps
isbn
md5
Some global identifiers have more than one value...
Coordinates
coordinates.lat =
coordinates.lng =
coordinates.alt =
Phone
phone.country_code = 1
phone.url = 9549549544
Profile
profile.pubkey = "1HQ..."
profile.name = "Satchmo"
profile.text = "Hello small world!"
profile.image = "b://98bcef1cc43ae..."
profile.banner = "https://www..."
If the above example is namespace as a noun, in this example we show a namespace can be used as a verb too. Actions usually don't need input data. Instead you act upon something that already exists. These begin new op_return chains instead of taking input from a previous protocol. This is useful if all you need is a single key to begin the chain, such as 'like'.
Please note: These are not intended to limit your imagination with how to organize the keys your app uses, but to give some basic examples of how you could do it. There are many viable methods for each of these cases:
Like something by txid:
Memo
0x6d04 txhash(32)
MAP
MAP SET 'like' 'true' tx <txhash>
Set your profile
Memo (multiple txs)
1. Set name
0x6d01 <name>(217)
2. Set profile text
0x6d05 <message>(217)
3. Set profile picture
0x6d0a <url>(217)
MAP
MAP SET 'profile.name' 'Satchmo' 'profile.text' 'Some cool text' 'profile.picture' 'b://986...'
Follow / Unfollow users
Memo
Follow user: 0x6d06 address(35)
Unfollow user: 0x6d07 address(35)
MAP
MAP SET 'follow.user' <address>
MAP SET 'unfollow.user' <address>
Follow / unfollow topic
Memo
Topic follow: 0x6d0d <topic_name>
Topic unfollow: 0x6d0e <topic_name>
MAP
MAP SET 'follow.topic' <topic_name>
MAP SET 'unfollow.topic' <topic_name>
Post
Memo
0x6d02 <message>(217)
MAP
B <message> <content-type> <encoding> | MAP SET 'type' 'post'
Reply to Tx
Memo
0x6d03 <txhash>(32) message(184)
MAP
B <message> <content-type> <encoding> | MAP SET 'type' 'reply' 'tx' <txhash> | AUTHOR_IDENTITY
Repost
Memo
0x6d0b <txhash> <message>
MAP
B <message> <content-type> <encoding> | MAP SET 'type' 'repost' 'tx' <txhash>
Topic Post
Memo
0x6d0c topic_name(variable) message(214 - topic length)
MAP
B <message> <content-type> <encoding> | MAP SET 'type' 'post' 'topic' <topic_name>
Comment on a URL without identity
B <message> <content-type> <encoding> | MAP SET 'type' 'comment' 'url' https://google.com
Comment with an identity
B <message> <content-type> <encoding> | MAP SET 'type' 'comment' 'url' https://google.com | AUTHOR_IDENTITY
Attach a picture to a geolocation
B <image> <content-type> <encoding> | MAP SET 'coordinates.lat' <latitude> 'coordinates.lng' <latitude> 'coordinates.alt' <altitude>
Comment on a phone number
B <message> <content-type> <encoding> | MAP SET 'phone.country_code' <country_code> 'phone.number' <phone_number>
Comment on a UPC code
B <message> <content-type> <encoding> | MAP SET 'type' 'comment' 'upc' <upc_code>
Lets say we want to create the following metanet structure:
We use metanet protocol to describe the relationship between the nodes, and MAP to define metadata on each node.
Bitcoin Schema is a declarative syntax for creating bitcoin transactions using META and MAP protocols.
Bmapjs can convert BitcoinSchema
{
"_bitcoinSchemaVersion": "0.0.1",
"profile": {
"name": "someone",
"interests": ["cars","science"],
"paymail": ["satchmo@handcash.io", "satchmo@moneybutton.com"]
}
}The key/value pairs will be converted to MAP commands yielding structure:
Tx 1 (Root node)
OP_FALSE
OP_RETURN
META
null
|
MAP
SET
_nodeName
profileTx 2 (child node)
OP_FALSE
OP_RETURN
META
<address>
<txid of root node>
|
MAP
SET
name
someone
:::
ADD
interests
cars
scienceMetaLens Comment
b14113a50b2d1c2a3644346662de921a60e1ed63bee9962dd4c7f7ee8a1f3ffb
[BMAP] [Genesis] [WhatsOnChain]
TonicPow Campaign Request
219776d570037ce56d4df03ba188999f093002a023413ffdb2b5d87bb18aeea0
[BMAP] [Genesis] [WhatsOnChain]
- Added
ADD,REMOVE,SELECT,CLEARcommands. - Added
:::command separator. - Updated readme.
Satchmo, Attila Aros
Thanks to Unwriter for the input and support.