Note
A character can create notes.
A note is typically an article published on the blockchain.
Create a Note
Assume that we have a character named Alice
with character ID 42
. Alice wants create a note as a blog post:
contract.note.post({
characterId: 42, // character ID
metadataOrUri: "https://example.com/note.json", // note metadata URL
});
Please refer to post
(opens in a new tab) for more details.
Note Metadata
The https://example.com/note.json
above is the URL of the note metadata. The note is a JSON file with the following structure:
{
"title": "Hello World",
"content": "This is a note."
}
It can be hosted on any web server. It can be ipfs://
URL. For example, here is another more complex note metadata: ipfs://bafkreidhjuuo2vibtevn66v2q6bjq652sfox6csocg6v2adwhywifcnzcy
(opens in a new tab).
You can create a note with metadata content object directly:
contract.note.post({
characterId: 42, // character ID
metadataOrUri: {
title: "Hello World",
content: "This is a note."
},
});
In this case, the SDK will automatically upload the metadata to IPFS and create a ipfs://
URL for you.
Please refer to Note Metadata for more details.
Link and Unlink a Note
You can use link to link a note to another note.
For example, a character 42
likes the note 5
written by another character 43
:
// character ID 42 likes note ID 43-5 (the 5th note of character 43)
const { data } = await contract.link.linkNote({
fromCharacterId: 42,
toCharacterId: 43,
toNoteId: 5,
linkType: 'like',
})
console.log(data) // linklist ID
// character ID 42 unlikes note ID 43-5
await contract.link.unlinkNote({
fromCharacterId: 42,
toCharacterId: 43,
toNoteId: 5,
linkType: 'like',
})
Please refer to Link Topics - Like and Unlike a Note for more details.
Note Link/Mint Module
Before attaching a link or a module to a note, please refer to the Concepts - Mint/Link Module section to learn more about what a module is.
Mint a Note
After creating a note, everyone (wallets) can mint it.
For example, to mint a note with characterId 42
and noteId 5
to the address 0x1234567890123456789012345678901234567890
:
const { data } = await contract.note.mint({
characterId: 42,
noteId: 5,
toAddress: '0x1234567890123456789012345678901234567890',
})
Now, the address 0x1234567890123456789012345678901234567890
owns the note NFT with contract address 0xabc123def456abc123def456abc123def456abc1
and token ID 6
(meaning that the note NFT is stored at the contract of address 0xabc123def456abc123def456abc123def456abc1
, and the minter is the 6th to mint).
This minted note NFT is of ERC721 type. It can be transferred to another address, or burned.
It deserves to be noted that minting might fail if the note is attached with a mint module that limits who can mint it. The app should handle this case.
Please refer to mint
(opens in a new tab) for more details.
Query Notes
You can query the notes from the indexer.
For example, to query the notes of character 42
:
const { list } = await indexer.note.getMany({
characterId: 42,
});
Topics
Reply to a Note
Reply
A character can reply to a note. This is done by using the contract.note.postForNote
(opens in a new tab) method:
// character ID 42 replies to note ID 43-5 (the 5th note of character 43)
const result = contract.note.postForNote({
characterId: 42, // character ID
metadataOrUri: { content: "Thank you!" }, // note metadata
targetCharacterId: 43, // target character ID
targetNoteId: 5, // target note ID
});
console.log(result) // The note ID of the reply, e.g. 1
To query all the replies of the note 43-5
:
const { list } = await indexer.note.getMany({
toCharacterId: 43,
toNoteId: 5,
});
console.log(list) // The list of notes that reply to note 43-5, which includes the note 42-1
Reply to a Reply (Thread)
Of course, the character 43
can also reply to the reply:
// character ID 43 replies to note ID 42-6 (the 6th note of character 42)
contract.note.postForNote({
characterId: 43, // character ID
metadataOrUri: { content: "You're welcome!" }, // note metadata
targetCharacterId: 42, // target character ID
targetNoteId: 1, // target note ID
});
This becomes a tree structure of notes:
43-5
└── 42-1
└── 43-6
This is commonly called a "note thread". In real-world applications, the thread might be more complex:
43-5
├── 42-1
│ ├── 43-6
│ └── 42-2
│ └── 43-7
└── 42-3
└── 43-8
We can query the whole thread recursively:
async function getNoteThread(
characterId: number,
noteId: number,
depth: number = 0,
): Promise<Note[]> {
const { list } = await indexer.note.getMany({
toCharacterId: characterId,
toNoteId: noteId,
});
if (depth === 0) {
return list;
}
const replies = await Promise.all(
list.map((note) =>
getNoteThread(note.characterId, note.noteId, depth - 1),
),
);
return list.concat(...replies);
}
Or using the includeNestedNotes
parameter to fetch them in one query (recommended):
const { list } = await indexer.note.getMany({
toCharacterId: 43,
toNoteId: 5,
includeNestedNotes: true,
nestedNotesDepth: 3, // 3 levels of replies, max is 3 for performance
nestedNotesLimit: 10, // 10 replies per level
});
Note Variant: "Achievement"
Publish an Achievement Note
A character can post a note as an achievement for others to mint.
This is done by setting the "variant"
field in the note metadata to "achievement"
. For example:
{
"variant": "achievement",
"title": "Sponsor NFT",
"content": "This is a sponsor NFT for my fans.",
"attachments": [
{
"mime_type": "image/png",
"address": "ipfs://example/sponsor_nft.png"
}
],
"attributes": [
{
"trait_type": "Tier",
"value": "Gold",
"display_type": "string"
}
]
}
contract.note.post({
characterId: 42, // character ID
metadataOrUri: "https://example.com/my-sponsor-nft.json", // note metadata URL
});
This note is not technically special; it just has a "variant"
field to indicate that it is an achievement NFT, and should be displayed as such in Apps.
Query Achievement Notes
Query Achievement Notes Published by a Character
You can query with the variant
field in indexer.note.getMany
to get the list of achievement notes published by a character
const { list } = await indexer.note.getMany({
characterId: 42,
variant: "achievement",
});
Query Achievement Notes Minted by an Address
You can query with the variant
field in indexer.mintedNote.getManyOfAddress
to get the list of achievement notes minted by an address
const { list } = await indexer.mintedNote.getManyOfAddress(
"0x1234567890123456789012345678901234567890",
{ variant: "achievement" },
);
Attach Mint Module
Just as other notes, an achievement note can be attached with a mint module.
One useful example is to limit the group of people who can mint the achievement note. In this case, ApproveMintModule
can be used.
contract.note.post({
characterId: 42, // character ID
metadataOrUri: "https://example.com/my-sponsor-nft.json", // note metadata URL
mintModule: {
address: "0x328610484ba1fAAE0fCDEe44990D199cD84c8608",
data: [
['0x1234567890123456789012345678901234567890', '0xabc123def456abc123def456abc123def456abc1'],
1,
]
},
});
The above code will attach a ApproveMintModule
to the achievement note, which means that only these two addresses can mint the achievement note, and each address can mint it only once (1
).
Restrict Minting
Approve Mint Module
Attach Approve Mint Module
As we have seen in the previous section, you can attach the mint module ApproveMintModule
to a note to restrict who can mint it.
contract.note.post({
characterId: 42, // character ID
metadataOrUri: "https://example.com/my-sponsor-nft.json", // note metadata URL
mintModule: {
address: "0x328610484ba1fAAE0fCDEe44990D199cD84c8608",
data: [
['0x1234567890123456789012345678901234567890', '0xabc123def456abc123def456abc123def456abc1'],
1,
]
},
});
In this example, only addresses 0x1234567890123456789012345678901234567890
and 0xabc123def456abc123def456abc123def456abc1
can mint the note, and each address can mint it only once (1
). This is called initData
.
Query Approve Mint Module
How do we recover the mint module information from a note? Here is an example.
For example, the above code posted a note 42-5
(Character ID: 42
, Note Id: 5
), you can query the mint module attached with indexer.mintModule.getMany
:
const { list } = await indexer.mintModule.getMany({
toCharacterId: 42,
toNoteId: 5,
});
const mintModule = list[0]; // there should be only one mint module for the specified note
console.log(mintModule);
The result will look like this:
{
"targetItemType": "Note",
"linkValue": "42-5",
"contractAddress": "0x328610484ba1faae0fcdee44990d199cd84c8608",
"initData": "0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000001234567890123456789012345678901234567890000000000000000000000000abc123def456abc123def456abc123def456abc1",
"returnData": "0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000001234567890123456789012345678901234567890000000000000000000000000abc123def456abc123def456abc123def456abc1",
"toCharacterId": 45,
"toNoteId": 188,
"operator": "0xc560eb6fd0c2eb80df50e5e06715295ae1205049",
"createdAt": "2023-03-30T16:26:10.000Z",
"updatedAt": "2023-03-30T16:26:10.000Z",
"deletedAt": null,
"transactionHash": "0x5ea8d21d8c6edd5ed5f37a00ac5fc31b536d8befec12d592b9e4bda8e8445a15",
"blockNumber": 29834475,
"logIndex": 1,
"updatedTransactionHash": "0x5ea8d21d8c6edd5ed5f37a00ac5fc31b536d8befec12d592b9e4bda8e8445a15",
"updatedBlockNumber": 29834475,
"updatedLogIndex": 1
}
initData
is the encoded init data string we passed in the previous section. We can decode it with decodeModuleInitData
:
import { decodeModuleInitData } from 'crossbell'
const decodedInitData = await decodeModuleInitData(
'0x328610484ba1faae0fcdee44990d199cd84c8608',
'0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000001234567890123456789012345678901234567890000000000000000000000000abc123def456abc123def456abc123def456abc1',
);
console.log(decodedInitData);
The result will look like this:
[
[
'0x1234567890123456789012345678901234567890',
'0xAbc123def456aBc123def456aBc123DEf456ABc1'
],
1n
]
which is the same as the initData
we passed in the previous section. Please note that the addresses are in formatted mixed-case (aka. a Checksum Address), and the number 1
is a bigint.