Introduction

The problem

  • Lot of decentralized groups : the community is scattered and very diverse from core developers to artist and gamers
  • No single entrypoint : There are plenty of tools, social networks and user registries
  • No effective information broadcast to all members : lot of scams, spams, bots or trollers
  • No global company/member directory : each application reimplements its own user registry bringing either duplication or separated worlds

Tezos pseudonym social graph

We are not going to tackle the problem of the unique identity like BrightID, instead we concentrate our effort on the pseudonymity already provided by Tezos addresses combined with grouping feature and restricted social media linking

  • Identity :
    1. strictly pseudonymous Tezos addresses
    2. (optional) restricted display name, alias and picture from different social networks available for people from same organization
  • Organizations : a way to regroup people inside a cluster
  • Internal alerting system : a non gas-free way to send important message to other clusters and get alerted on real time

Final goal

This application :

  • should not be seen as a final product but as a base layer for other application to integrate with. Example : providing a list of player for a mobile game where it is hard to type and remember a tz1 address
  • does not require high presence. It has to be seen as a configuration tool like a contact list, a LDAP or other user registries running in background

User Guide

Login

Login with your preferred wallet, configure your social account link, claim your NFT membership card

Create an organization

Create an organization, be the administrator and start to manage it

Manage an organization

Manage an organization, approve/refuse members and add more administrators

Join an organization

Join an existing organization

Write to an organization

Write to an organization and receive replies and notifications

Login

Open/Connect to Tezos community

Your identity is your wallet account

Click on Connect your wallet and select your preferred wallet and account

Link

You can choose to link your profile with one of the listed social account. If you do so, other people from same group are able to see your real name and picture. It will improve human readable identities for others third party applications, messaging, etc ...

Note : if you want to stay totally pseudonymous, don't do this step

Click on bottom button Profile and select of the social account you have

Note : if your social account does not exist on the list , please send an email to benjamin.fuentes@marigold.dev

Once you confirm all intermediate popups, your Tezos address will now appear with the social account information

You can unlink social account at any time clicking on button UNLINK SOCIAL ACCOUNT

Claim NFT membership card

On MEMBERSHIP tab, you can claim your Tezos NFT membership card for free

It can be used later for loyalty programs, access token, etc ...

Click on CLAIM NFT button

Logout

On top right corner of Profile menu, click on LOGOUT button to disconnect your wallet

Create an organization

On Home/Organizations menu, click on CREATE AN ORGANIZATION and fill the form then click on DONE button

Confirm the transaction with your wallet, and wait few seconds for refresh

  • name : The name of your organization
  • business : The goal or business of your org
  • IPFS NFT url (optional): the link to your org NFT
  • Logo url (optional): the picture / logo
  • Website (optional): the link to your website
  • Funding address (optional): the address to your DAO or a user wallet address for sending funds
  • AutoRegistration (optional): if you authorize or not member to join automatically without manual approval. Default is false

If you click on it, it is selected and you have access to different tabs

Note : your organization is still on pending mode until a super admin will look at it and do a short KYC with you. When this process is accepted, you will be able to accept new members, etc ... Note : You will have to behave as a good father/mother and not have an unappropriated behavior. If members reports so, your organization might be frozen during a certain time. Apart of this, super admins have no control on what you do on your organization.

Update

As administrator of your org, you can update all the fields and click UPDATE ORGANIZATION

Messages

You can vizualize here all alerts/messages sent to your organization

For more information on how to send message, got to this chapter

Administration

The last tab is where you manage your organization, go to this chapter to know more about it

Manage an organization

If you are administrator, on your Organization page, the last tab lets you manage your org

Administrators

Add administrator

Enter the address of someone you want to invite as administrator and click on ADD ADMIN

Once confirmed he/she will appear on both admin and member

Delete admin

Click on the trash to delete an admin

Now you are the only admin.

Note : as there is at least 1 member on your organization (that is not you), you can pass the admin flag to another member or either decide to delete your organization

Members

Users can be added only if :

  • they send a request to join and are accepted (with AutoRegistration to false)
  • they send a request to join (with AutoRegistration to true)
  • they are no more admins

You can only remove a user from organization, no other action are possible.

To remove a user

Click on the trash icon

Member requests

To have member requests, you need someone to send a join request, see this chapter for more details

When you have user, you can approve/decline in batch

For each user, approve or remove it from selection clicking on each checkbox. When you are done, click on APPLY ALL button to confirm all

If you accepted users, they will be part of member list now

Otherwise, they are discarded

Join an organization

Any user can request to join an organization

On home/Organization menu, click on JOIN AN ORGANIZATION

Fill the reason and select the organization you want to join, then click on DONE

Your request is sent to the administrator of this organization

To see what an administrator can do with your request , click here

Write to an organization

Any user can write to an organization, being part of it or not but ... he/she will have to pay gas fees. The reason is that we want to avoid spammers and we want to make these alerts/messages appearing as important information

Write

You can either click on the button WRITE TO ORGANIZATION on Home/Organization page or on the MESSAGES tab of your organization

Write your message (respecting ASCII characters) and click on SEND button

Reply

If someone send a message and is not part of the organization, he/she cannot see the answers. To tackle this issue, any user can decide to reply to a message.

Click on REPLY on a specific message and add your text there, then click on REPLY button

Watch replies in thread

If you are part of the organization, you can vizualize all thread and replies

Watch replies on inbox

If you are not part of the organization, you can vizualize replies to your messages on your Profile > Inbox tab

Developer Guide

Incentive

Why to integrate TzCommunity and how it is different to other name resolvers

PreRequisites

What do you need to integrate TzCommunity

Install

Explanation step by step

Incentives

All applications manage their own identities or delegate it to a kinda LDAP system. What is important to figure out is that end users don't want to face non human readable identifiers (unless pseudonymity is the main purpose).

TzCommunity offers you, for free, a way to convert identifiers to trustable human readable names, and leverage your role based access system

  • linking your Tezos address is free, no hidden fees, no need to renew, no naming spoilation
  • unlinking too
  • social account link is trustable, it is based on OAUTH2, noone can impersonate you as we provide you the real provider alias identifier
  • you can organize groups and manage your role based access system in your application based TzCommunity organizations

Compared to other solutions :

Feature/SolutionTezos DomainsWallet contact bookTzCommunity
publicly availableyesnoyes, only people from same organizations
costfrom 1 to 100tz00
dapp integrationyesnoyes
reuse your name (even if it is already taken)noxyes
preserve pseudnomitynoyesyes (just don't link your account)

Pre-requisites

Client libraries are focused on Typescript/Javascript web based applications (or Hybrid like Ionic)

  • tezos-community : base code containing TzCommunity Typescript classes for objects and entrypoints. It is generated by Taqueria plugin to generate types. It includes also a caching service and the functions to authenticate on TZCommunity backend API
  • tezos-community-reactcontext : based on top of the previous library, it adds a React context to help you propage UserProfiles among your dapp
  • tezos-community-reactcontext-ionic : based on top of the previous library, it add some specific custom components like an IonChip that resolves a Tezos address to a name, picture and social account logo

Install

Depending of your dapp technology, you can import 1,2 or the 3 libraries

npm i @marigold-dev/tezos-community
npm i @marigold-dev/tezos-community-reactcontext
npm i @marigold-dev/tezos-community-reactcontext-ionic

Here below is described the full solution for Ionic React.

You can start with the base library and adapt the code for an Angular context or Ionic Angular with no problem. If requested we could publish libraries for other web frameworks

Set environment variables

Library will search for 2 variables :

  • VITE_TZCOMMUNITY_CONTRACT_ADDRESS : the TzCommunity deployed smartcontract
  • VITE_TZCOMMUNITY_BACKEND_URL :the TzCommunity backend API url

Ghostnet

VITE_TZCOMMUNITY_CONTRACT_ADDRESS=KT1KpqE5kfor9fmoDTNMAjoKeLpKRv4ooEZw
VITE_TZCOMMUNITY_BACKEND_URL=https://back.tezos-community.gcp-npr.marigold.dev

Mainnet

VITE_TZCOMMUNITY_CONTRACT_ADDRESS=KT1D95aUDDfCQnh1Ay7riNaTDaWwX63dGY6X
VITE_TZCOMMUNITY_BACKEND_URL=https://back.tezos-community.gcp.marigold.dev

These values might possibly change in the future, to be sure to use the correct configuation, contact Marigold Team

Connect / Disconnect to TzCommunity backend

TzCommunity uses SIWT to authentify to a web2 backend with your wallet TzCommunity overrides default web local storage with Ionic one : import { Storage as LocalStorage } from "@ionic/storage"; . this way it works both for web and Ionic mobile app on native devices

Connect and fetch your own user Profile

Import libs

import {
  TzCommunityReactContext,
  TzCommunityReactContextType,
} from "@marigold-dev/tezos-community-reactcontext";
import { address } from "../type-aliases";
import { getUserProfile } from "@marigold-dev/tezos-community";

Import the React context

const {
  setUserProfile,
  connectToWeb2Backend,
  localStorage,
  setUserProfiles,
  userProfiles,
} = React.useContext(TzCommunityReactContext) as TzCommunityReactContextType;

Connect to TzCommunity backend just after your logged with your wallet on Beacon. You can use setUserProfile to set your profile to a global context like below

const connectWallet = async (): Promise<void> => {

    ...

    try {
      await wallet.requestPermissions({
        network: {
          type: NetworkType[
            import.meta.env.VITE_NETWORK.toUpperCase() as keyof typeof NetworkType
          ],
          rpcUrl: import.meta.env.VITE_TEZOS_NODE,
        },
      });
      // gets user's address
      const userAddress = await wallet.getPKH();
      await setup(userAddress);

      //connect to TzCommunity
      await connectToWeb2Backend(
        wallet,
        userAddress,
        (
          await wallet.client.getActiveAccount()
        )?.publicKey!,
        localStorage
      );

      //try to load your user profile
      try {
        const newUserProfile = await getUserProfile(userAddress, localStorage);
        setUserProfile(newUserProfile!);

        setUserProfiles(
          userProfiles.set(userAddress as address, newUserProfile!)
        );
      } catch (error) {
        console.warn(
          "User " +
            userAddress +
            " has no social account profile link on TzCommunity"
        );
      }

Disconnect

On your disconnect function, remove SIWT tokens from local storage

  const disconnectWallet = async (): Promise<void> => {

    //TzCommunity
    if (localStorage.initialized) {
      console.log("localStorage is initialized, removing access tokens");
      await localStorage.remove(LocalStorageKeys.access_token); //remove SIWT tokens
      await localStorage.remove(LocalStorageKeys.id_token); //remove SIWT tokens
      await localStorage.remove(LocalStorageKeys.refresh_token); //remove SIWT tokens
    } else {
      console.warn("localStorage not initialized, cannot remove access tokens");
    }
    //End TzCommunity

On App.tsx, Load user profiles, and cache results

Initialize your local storage at Application startup

useEffect(() => {
  //TzCommunity
  (async () => {
    await localStorage.initStorage();
  })();
  //End TzCommunity
}, []);

Try to feth all user profile once you have connected

//tzCommunity
const [userProfiles, setUserProfiles] = useState<Map<address, UserProfile>>(
  new Map()
);

const [userProfile, setUserProfile] = useState<UserProfile | undefined>();
const [localStorage, setLocalStorage] = useState<CachingService>(
  new CachingService(new LocalStorage())
);

useEffect(() => {
  //only try to load if userProfile, it means you are logged with TzCommunity
  (async () => {
    if (userProfile || userProfile === null) {
      try {
        setUserProfiles(
          await loadUserProfiles(Tezos, userAddress!, localStorage)
        );
      } catch (error) {
        console.log(error);

        if (error instanceof TzCommunityError) {
          switch (error.type) {
            case TzCommunityErrorType.ACCESS_TOKEN_NULL: {
              console.warn("Cannot refresh token, disconnect");
              disconnectWallet();
              break;
            }
            case TzCommunityErrorType.ACCESS_TOKEN_EXPIRED: {
              console.warn(
                "Access token expired, try to fetch from refresh token.."
              );
              await refreshToken(userAddress!, localStorage);
              const userProfile = await getUserProfile(
                userAddress!,
                localStorage
              );
              if (userProfile) setUserProfile(userProfile);
              setUserProfiles(
                await loadUserProfiles(Tezos, userAddress!, localStorage)
              );
              break;
            }
          }
        } else {
          //nada
        }
      }
    } else {
      //nada
    }
  })();
}, [userProfile]);

Wrap your application with TzCommunity React context, so you can inject the context everywhere on your pages

return (
  <IonApp>
    <TzCommunityReactContext.Provider
      value={{
        userProfiles,
        setUserProfiles,
        userProfile,
        setUserProfile,
        localStorage,
        connectToWeb2Backend: connectToWeb2Backend,
      }}
    >
      ....
    </TzCommunityReactContext.Provider>
  </IonApp>
);

In case of HARD page refresh, lost of session and you want to reload your user profile

const reloadUser = async (): Promise<string | undefined> => {

const activeAccount = await wallet.client.getActiveAccount();

if (activeAccount) {
    let userAddress = activeAccount!.address;
    setUserAddress(userAddress);

...

//try to load your user profile
try {
  const newUserProfile = await getUserProfile(userAddress, localStorage);
  setUserProfile(newUserProfile!);

  setUserProfiles(userProfiles.set(userAddress as address, newUserProfile!));
} catch (error) {
  if (error instanceof TzCommunityError) {
    switch (error.type) {
      case TzCommunityErrorType.ACCESS_TOKEN_NULL: {
        console.warn("Cannot refresh token, disconnect");
        disconnectWallet();
        break;
      }
      case TzCommunityErrorType.ACCESS_TOKEN_EXPIRED: {
        console.warn("Access token expired, try to fetch from refresh token..");
        await refreshToken(userAddress!, localStorage);
        const userProfile = await getUserProfile(userAddress!, localStorage);
        if (userProfile) setUserProfile(userProfile);
        setUserProfiles(
          await loadUserProfiles(Tezos, userAddress!, localStorage)
        );
        break;
      }
    }
  } else {
          console.warn(
            "User " +
              userAddress +
              " has no social account profile link on TzCommunity"
          );
        }
      }

      return userAddress;
    } else {
      return undefined;
    }
  };

On a Page, Replace tz1 display on your UI by this custom IonChip

Import libs, fetch profiles from context and use the Chip component (replacing alice tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb address by your own variable)

import {
  TzCommunityReactContext,
  TzCommunityReactContextType,
} from "@marigold-dev/tezos-community-reactcontext";
import { TzCommunityIonicUserProfileChip } from "@marigold-dev/tezos-community-reactcontext-ionic";
import { address, int } from "../type-aliases";

...


 //TZCOM CONTEXT
  const { userProfiles } = React.useContext(
    TzCommunityReactContext
  ) as TzCommunityReactContextType;

...

return  <TzCommunityIonicUserProfileChip
                userProfiles={userProfiles}
                address="tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb"
                key="tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb"
              ></TzCommunityIonicUserProfileChip>


Here is the end of the magic story

Example

You can find a full mobile dapp integration of these libraries on TzVote code

Contributors

Made with love by Marigold team