npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

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:

  1. Authentication
  2. Graph Database service.
  3. PubSub service.
  4. Image Upload service.
  5. MNS (Myobu Name Service).
  6. DAO service.

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, and null types in props 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