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 :
- strictly pseudonymous Tezos addresses
- (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
Links
- TEZOS COMMUNITY DAPP (Web based)
- TEZOS COMMUNITY DAPP (Android) Soon
- TEZOS COMMUNITY DAPP (iOS) Soon
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/Solution | Tezos Domains | Wallet contact book | TzCommunity |
---|---|---|---|
publicly available | yes | no | yes, only people from same organizations |
cost | from 1 to 100tz | 0 | 0 |
dapp integration | yes | no | yes |
reuse your name (even if it is already taken) | no | x | yes |
preserve pseudnomity | no | yes | yes (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