myobu-protocol-client
v0.1.0
Published
**Work in progress**
Downloads
6
Readme
Myobu Protocol
Work in progress
Myobu as a Service
https://protocol.myobu.io
We provide a protocol service for Myobu, which offers:
- Authentication
- Graph Database service.
- PubSub service.
- Image Upload service.
- MNS (Myobu Name Service).
- DAO service.
- Myobu Protocol
Client code
import { MyobuProtocolClient } from "myobu-protocol-client";
import { ethers } from "ethers";
const signer = ... //get ethers signer...
const client = new MyobuProtocolClient({
signer, // Needs the wallet to sign transactions
server: "https://protocol.myobu.io/",
expiresIn: 1000 * 60 * 60 * 24 * 365, // 1 year
});
Authentication
The user will be authenticated using the signer
provided in the constructor.
Whenever the user needs to be authenticated, the client will sign a message using the signer
and send it to the server to verify.
Database
Models
Node
A node has labels
to identify its type and props
(properties) that describe its data.
You can create a node in Myobu Protocol like the below:
const profileNode = await client.queryDB({
match: [
{
key: "profile",
labels: ["MNS"],
props: {
name: "kirito",
},
},
],
return: ["profile"],
});
console.log(profileNode);
/*
{
"labels": ["MNS"],
"props": {
"name": "kirito",
"email": "[email protected]",
"created": 1620000000000,
"modified": 1620000000000,
"age": 18
// System inserted props
"_id": "0x0000000000000000000000000000000000000000000000000000000000000001",
"_owner": "0x0000000000000000000000000000000000000000",
"_nftId": "0x0000000000000000000000000000000000000000000000000000000000000000", // null if no NFT is attached
},
}
*/
In the future, the number of Myobu you hold (or staked) will decide how many nodes you can create.
Node labels are strictly required to be camel-case, beginning with an upper-case character. VehicleOwner rather than vehicleOwner etc.
We only support
string
,number
,boolean
, andnull
types inprops
for now.Property names that start with
_
are reserved for system use. Users are not allowed to modify them. For example,_owner
,_createdAt
,_updatedAt
.
Relationships
A relationship is a directed connection between two nodes.
A relationship has a type
and props
(properties) to describe the relationship.
{
match: [
{
...profileNode,
key: "profile",
},
],
create: [
{
key: "r",
type: "LIVES_IN",
from: { key: "profile" },
to: {
key: "city",
labels: ["City"],
props: {
name: "Tokyo",
},
},
props: {
since: 2019,
},
},
],
return: ["profile", "city", "r"],
};
In the future, the number of Myobu you hold (or staked) will decide how many relationships you can create.
Relationship types are strictly required to be upper-case, using underscore to separate words. :OWNS_VEHICLE rather than :ownsVehicle etc.
Operations
Upsert (Create or Update) using merge
{
merge: [
{
key: "profile",
labels: ["MNS"],
props: {
name: "kirito",
},
onMatch: {
"profile.fetched": { "$timestamp": true },
},
onCreate: {
"profile.created": { "$timestamp": true },
"profile.fetched": { "$timestamp": true },
},
}
],
return: ["profile"]
}
Query
// Find people who lives in Mars and has age greater than 18 and less than 90
const result = await client.queryDB({
match: [
{
key: "r",
type: "LIVES_IN",
from: {
key: "people",
labels: ["MNS"],
},
to: {
key: "city",
labels: ["City"],
props: {
name: "Mars",
},
},
props: {
// ...
},
},
],
where: {
$and: [
{
"people.age": {
$gt: 18,
},
},
{
"people.age": {
$lt: 90,
},
},
],
// We also support $gt, $gte, $lt, $lte, $ne, $eq, $in, $nin, $regex, $contains, $startsWith, $endsWith
},
skip: 10,
limit: 5,
orderBy: {
"people.age": "DESC",
},
return: ["people"],
});
Update
{
match: [
{
key: "people",
labels: ["MNS"],
props: {
name: "kirito",
},
},
],
skip: 10,
limit: 5,
set: {
"people.name": "newkirito",
"people.age": {
$add: ["$people.age", 1],
},
"people.followers": {
$coalesce: [
{
$add: ["$people.followers", 1],
},
1, // If followers is null, set it to 1
],
},
},
return: ["people"],
};
Delete
{
match: [
{
key: "r",
type: "LIVES_IN",
from: {
key: "people",
labels: ["MNS"],
props: {
// ...
},
},
to: {
key: "city",
labels: ["City"],
props: {
name: "Mars",
},
},
props: {
// ...
},
},
],
skip: 10,
limit: 5,
delete: ["people", "r", "city"],
detachDelete: ["city"], // When you want to delete a node and any relationship going to or from it, use DETACH DELETE.
return: ["people"],
};
Constraints for Label
List constraints
await client.listLabelConstraints("MNS");
Create constraints
await client.createLabelConstraints({
label: "MNS",
unique: [["_owner"], ["name"]],
});
Delete constraints
await client.deleteLabelConstraints(["constraint_name_1", "constraint_name_2"]);
Ownership
Each node has an owner. The owner of the node could transfer its ownership to another address only if the node has taken snapshot as NFT.
Snapshot
Store nodes, and relationships (subgraph) data in a snapshot on blockchain.
// TODO: Implement the related smart contract
await client.takeSnapshot(nodeId);
Label schema
If you own the label, you can define the schema of the label. Then when other users create/update the nodes with the label, the schema will be checked.
- Set label schema
await client.setLabelSchema({
label: "Address",
properties: {
lines: {
type: "array",
items: { type: "string" },
},
zip: { type: "string" },
city: { type: "string" },
country: { type: "string" },
},
required: ["country"],
});
- Get label schema
const schema = await client.getLabelSchema(labelName);
- Delete label schema
await client.deleteLabelSchema(labelName);
Label ACL
Access Control List (ACL) is a list of permissions for a label.
- Set label ACL
await client.setLabelACL({
label: "Address",
node: {
write: {
// Create | Update | Delete
minBalance: 100, // Minimum Myobu balance you need to create a node with this label. Check `DAO` section later.
minVotingPower: 100, // Minimum voting power you need to create a node with this label. Check `DAO` section later.
},
},
});
- Get label ACL
await client.getLabelACL(labelName);
- Delete label ACL
await client.deleteLabelACL(labelName);
Event
To be implemented
You can set event for the database.
Please note that only the label owner can set event for the label.
~~And the label owner can only run the set
db operation to update the properties with the name that starts with _
in the trigger for the node of the label owned.~~
await client.createDBEvent({
name: "follows",
label: "MNS",
description: "User follows another user",
params: ["followeeId"],
db: {
match: [
{
key: "user",
labels: ["MNS"],
props: {
_owner: { $signer: true }, // $signer here means the address who calls the event
},
},
{
key: "followee",
labels: ["MNS"],
props: {
_owner: { $arg: "followeeId" }, // We use $arg to get the argument passed to the event
},
},
],
merge: [
{
key: "r",
type: "FOLLOWS",
from: {
key: "user",
},
to: {
key: "followee",
},
},
],
set: {
// The `owner` can set properties with the name that starts with `_`, unlike the normal user.
"user._followings": {
$coalesce: [
{
$add: [{ $key: "user._followings" }, 1],
},
1, //
],
},
"followee._followers": {
$coalesce: [
{
$add: [{ $key: "followee._followers" }, 1],
},
1,
],
},
},
},
});
Then we can apply the db event like below:
await client.applyDBEvent({
label: "MNS",
name: "follows",
args: {
followerId: "0x1234567890",
},
});
PubSub
const { unsubscribe, emit } = await client.pubsub(roomId, (event) => {
// ...
});
emit("Message"); // Send message to `roomId`
unsubscribe(); // Unsubscribe from `roomId`
Image upload
We upload images to imgur.com using their nice API.
const files: File[] = []; // Your image files.
const { urls } = await client.uploadImages(files);
DAO
Getting implemented
Balance
Get the user balance. This includes the amount of MYOBU tokens (on both ETH and BNB) that the user holds and the amount of MYOBU tokens that the user has staked.
const walletAddress = "0x1234567890";
const balance = await client.getBalance(walletAddress);
Voting power
Get the user's voting power. The voting power is the weighted balance of the user's staked MYOBU token (on both ETH and BNB).
const walletAddress = "0x1234567890";
const votingPower = await client.getVotingPower(walletAddress);
Delegation
To be implemented
DAO proposal
To be implemented
DAO proposal support
// Create a proposal
const proposal = {
title: "Proposal title",
description: "Proposal description",
voteType: "SINGLE_CHOICE", // SINGLE_CHOICE, MULTI_CHOICE, RANKED_CHOICE
choices: [
{
description: "Choice 1 description",
},
{
description: "Choice 2 description",
},
],
startAt: new Date().getTime(),
endAt: new Date().getTime() + 1000 * 60 * 60 * 24 * 7, // 7 days
minVotingPower: 100, // Minimum voting power required to vote the proposal
};
const _proposal = await client.createProposal(proposal);
// Get existing proposal by Id
const __proposal = await client.getProposal(_proposal.id);
// Delete owned proposal
await client.deleteProposal(_proposal.id);
// Update title and description
const _proposal = await client.updateProposal(_proposal.id, {
title: "New title",
description: "New description",
});
// Add choice
const choice = {
description: "Choice 3 description",
};
const _proposal = await client.addProposalChoice(_proposal.id, choice);
// Remove choice
const _proposal = await client.removeProposalChoice(_proposal.id, choice.id);
// Vote for a proposal
await client.voteForProposal(_proposal.id, [
{
id: _proposal.choices[0].id,
},
{
id: _proposal.choices[1].id,
},
]);
// Unvote for a proposal
await client.unvoteForProposal(_proposal.id);
MNS (Myobu Name Service)
// Create an MNS
const profile = await client.upsertMNS({
name: "kirito",
displayName: "Kirito",
/*
email?: string;
avatar?: string;
wallpaper?: string;
description?: string;
// Social medias
url?: string;
twitter?: string;
discord?: string;
github?: string;
telegram?: string;
reddit?: string;
youtube?: string;
instagram?: string;
facebook?: string;
tiktok?: string;
twitch?: string;
linkedin?: string;
// Wallet addresses
eth?: string;
btc?: string;
*/
});
// Get an MNS
// * by name
const profile = await client.getMNS("kirito");
// * by wallet address
const profile = await client.getMNS("0x1234567890");
// Get minimum balance required to create an MNS
const minBalanceRequiredToCreateMNS =
await client.getMinBalanceRequiredToCreateMNS();
// Follow
await client.followMNS("kirito");
// Unfollow
await client.unfollowMNS("kirito");
Useful tools
- https://gitcdn.link/
References
- https://docs.snapshot.org/proposals/voting-types