# Introduction

Bitcoin rose out of necessity from the ashes of the [Great Recession](https://threadreaderapp.com/thread/1390167542744768512.html). It is a result of people waking up to the fact that the few who control the power are abusing that power as a quiet tool of injustice. We are now living in the middle of the age of the Great Censorship. A few elites hold the power to moderate our information flow, and this power is, more often than not, used as political weapons to spread misinformation and manufacture consent.

> Necessity = the mother of invention after all - @redphonecrypto

We observe a need for an open and decentralized social protocol as a public good, to provide a pro-social alternative to other existing networks and protocols.

There are no shortage of decentralized social [protocols](https://matrix.org/_matrix/media/r0/download/twitter.modular.im/981b258141aa0b197804127cd2f7d298757bad20) today. One of the first that gain meaningful adoption is [Mastodon](https://joinmastodon.org/) using a federated approach. Some protocols focus more on avoiding singletons, such as [Scuttlebutt](https://scuttlebutt.nz/), as well as others that try to align incentives with its users, such as [Steem](https://steem.com/SteemWhitePaper.pdf) and [BitClout](https://bitclout.com/).

It is a difficult and unique problem that many others are trying to [solve](https://threadreaderapp.com/thread/1348894400919703552.html). Why another, and why now?

We believe that for such a social network to be credible, it must exist as a public good; a platform token that’s designed to capture value from the network ultimately takes values away from the network, setting up weird incentives alignment between the platform and the users.

We also believe that such a social network must be non-rivalrous, and should incentivize its “rivals” to build on top of one another, rather than playing a zero-sum game that promotes a winner-takes-all mentality.

We took bits and pieces from many protocols before us, and have made some tweaks with the above missions in mind. We found that building on top of ETH and ENS while keeping the option to integrate with other naming systems in the future, plus a decentralized data component with a centralized search & discovery API, to be the most promising in terms of delivering such ideals.


# UI Clients

https\://github.com/zkitter/ui

**Web UI**

<img src="/files/9CHcPD51i5HERjpFowm7" alt="" data-size="line"> [Zkitter](https://zkitter.com) - microblogging

<img src="/files/SqHlWcG3uy3RRWDNiVaO" alt="" data-size="line"> [Auti.sm](https://auti.sm) - microblogging


# Overview

**ZK Social** is a zero-knowledge social network built on top of four different layers:\
\- Identity layer (Ethereum - decentralized)\
\- Data layer (GunDB - decentralized)\
\- API layer (Postgres - centralized)\
\- UI layer (Typescript/React - centralized)

![Network Diagram](/files/GHvajCcFkVMl4i5W0NN3)

Each client runs a *GunDB* peer node that joins a peer-to-peer network with the seed nodes `gun-seed-1.auti.sm/gun`. Each client by default only download and keep just the amount of data they need from GunDB, but they can also download and retain records of all users to ensure data completeness in the network. We will initially operate three seed nodes that keep records of all data.

In order to join as a user, a user will first have to generate a *ECDSA* key pair, and write the public key to a [smart contract](https://arbiscan.io/address/0x6b0a11f9aa5aa275f16e44e1d479a59dd00abe58) in Arbitrum (support for other networks will come in the future). The private key is used to authenticate the user's write access to GunDB, where the public key is used by other clients to retrieve a user's data slice.

To help with discovery of contents, we also run SQL indexer that index all data into queryable formats and [serve](https://arbiscan.io//tx/0x9a75d0c20b846810194c129407eaf6541440ad75ca1d92d557be081cd01ddd37#eventlog) them over a RESTful API.


# Identity

We will initially use EVM address and ENS names as usernames for users. As ENS is also used as an alias for wallet address, building on top of ENS also make supporting feature such as subscription and gifting simpler as we can simply look up the user’s wallet address from the ENS contract (note that wallet address is the same across different EVM chain).

To onboard, a user must generate an ECDSA key pair, and add the public key to the [Registrar contract](https://github.com/autism-org/contracts). This new ECDSA key pair is derived by producing a SHA-256 hash of the result of calling `web3.eth.person.sign('Sign this message to generate a GUN key pair with key nonce: 0')`.

The goal of using derived key achieve three purposes:

* to allow users to *authenticate their social messages without risking their primary wallet keys*, as the risk profile of a wallet private key (access to fund) is much higher than one’s social identity
* to allow users to *update a new key pair by incrementing the message nonce* in the case when their previous key is compromised
* to allow users to *recover their social identity using their wallet* without having to remember another seed phrase.

The Registrar contract consists of two major functions:

* **`update(bytes pubkey)`**: update `pubkey` for the `msg.sender`
* **`updateFor(address account, bytes pubkey, bytes proof)`**: update `pubkey` for `adress` as long as `proof` is valid using `ecrecover`

Each update will emit a `RecordUpdate` event. Anyone can then watch for those events and index all users’ identities into whatever database they so choose. The reference implementation of the [indexer node](https://github.com/autism-org/indexer) uses postgres to store all users’ identities.

**Support for Other Blockchain and Naming Systems**

We believe that composability is one of the main source of innovation in crypto, and plan to include support any naming system based on demand in the future. We categorize these systems into two broad categories:

* **Smart-Contract Platform**: systems such as BSC, DOT, and SOL that supports smart contract can be supported by deploying similar contract to their platform.
* **DNS-based Naming System**: systems such as traditional domain name and decentralized naming system (i.e. [*bob.com*](http://bob.com/), *alice.crypto*) can be supported by requiring a `TXT` record update under the format `autism:<pubkey>`, of which the indexer can then watches for record updates from those domain.

These should cover the majority of different identities solution exist today.


# Pseudonymous Identity

> “Man is least himself **when** he talks in his own person. **Give him a mask**, and he will **tell you the truth**.” - Oscar Wilde

The rise of cancel culture puts a new type of self-directed censorship to society. We no longer feel safe expressing our true opinions, because we fear that it might one day come back to haunt us. The more controversial an opinion gets, the least incentive we have communicating it publicly, regardless how strongly we feel about such opinion. Over time, we are trained to simply comply with the mainstream point of view.

We want to bring new primitives to enable [pseudonymous economy](https://www.youtube.com/watch?v=gkJqMSbI1IA) by allowing users to diversify their reputation across different names. One way to make that happen is to enable the anonymous transfers of reputation.

[Interep](https://docs.interep.link/) is a reputation mixer where users meeting certain criteria (e.g. any Twitter users with at least 10 followers) can join a group, and later on prove that they are members of the group without exposing their actual identity. For details on how that works, check out how we [generate](https://github.com/autism-org/ui/blob/main/src/ducks/drafts.ts#L139-L159) and [verify](https://github.com/autism-org/indexer/blob/main/src/services/gun.ts#L277-L305) semaphore proofs.


# Data

GunDB gives us a peer-to-peer data layer that's always open and ensures that anyone building on top of Autism will always have 100% of the data available to them.

<br>


# Schema

[Reference Implementation](https://github.com/autism-org/ui/blob/main/src/util/message.ts)<br>

Every activity performed by a user can be described as a message with the follow schema:

**Message**

```json
{  
    id: STRING[255],  
    type: STRING[15],  
    subtype: STRING[15],  
    creator: STRING[65535],  
    createdAt: UINT64,  
    payload: OBJECT, // Depends on message type  
}  
```

| Name      |       Type      |                                                                           Description |
| --------- | :-------------: | ------------------------------------------------------------------------------------: |
| id        |  `STRING[255]`  | Unique message ID based on the content hash of the message. See “Message ID” section. |
| type      |   `STRING[15]`  |                               Main type describing the primary purpose of the message |
| subtype   |   `STRING[15]`  |                              Sub type describing the secondary purpose of the message |
| creator   | `STRING[65535]` |                         Domain names of the creator (e.g. bob.eth, alice.crypto, etc) |
| createdAt |     `UINT64`    |                             Unix timestamp in seconds of when the message was created |
| payload   |     `OBJECT`    |                                   Schema containing data of a message depends on type |

**Message ID**

We should hex-encode the message based on the following pseudo code in order to create a deterministic SHA256 hash.

```js
const type = hexString(message.type.toUpperCase()); // e.g. "POST" -> 504f5354  
const subtype = hexString(message.subtype.toUpperCase());  
const creator = hexString(message.creator); // ETH address
const createdAt = hexUint32(message.createdAt); // e.g. 1624773198 -> 60D8124E  
const payload = hex(message.payload); // hex order based on message types  
const encode = [  
  uint8len(hash), hash, // "POST" -> 504f5354 -> 8504f5354  
  uint8len(subtype), subtype, // "" -> "" -> 0  
  uint16len(creator), creator, // "bob.eth" -> 626f622e657468 -> 000e626f622e657468  
  createdAt, // 1624780498 -> 0000000060d82ed2  
  payload  
].join('');  
  
const messageHash = crypto.createHash('sha256').update(encode).digest('hex');  
const messageId = creator + '/' + messageHash; // e.g. 0x1234...7890/42740f20aed483b69701a55ab295a2ed  
```

A message can be used to described user activities such as a post, comment, likes, and follow.

**Profile Message**

The PROFILE type is used to support adding profile data to a name.

```json
{  
	type: "PROFILE",  
	subtype: "NICKNAME" | "BIO" | "PROFILE_IMAGE",  
	payload: { 
		value: STRING[255],
	}  
}  
```

| Name  |      Type     |               Description |
| ----- | :-----------: | ------------------------: |
| value | `STRING[255]` | value of the profile data |

```json
{  
	type: "PROFILE",  
	subtype: "CUSTOM",  
	payload: {  
		key: STRING[255],  
		value: STRING[16777215]  
	}  
}  
```

| Name  |        Type        |                  Description |
| ----- | :----------------: | ---------------------------: |
| key   |    `STRING[255]`   |   Custom key of profile data |
| value | `STRING[16777215]` | Custom value of profile data |

**Post Message**

The POST type is used to support posts (similar to tweets, status update, etc), comments, and reposts.

```json
{  
	type: "POST",  
	subtype: "" | "REPLY" | "REPOST",  
	payload: {  
		topic: STRING[255],  
		title: STRING[255],  
		content: STRING[16777215],  
		reference: STRING[255],  
		attachment: STRING[255]  
	}  
}  
```

| Name       |        Type        |                                                  Description |
| ---------- | :----------------: | -----------------------------------------------------------: |
| topic      |    `STRING[255]`   | Topic of the post for ease of discovery. (e.g. sports, news) |
| title      |    `STRING[255]`   |                  Plain text containing the title of the Post |
| content    | `STRING[16777215]` |                 Markdown text containing content of the Post |
| attachment |    `STRING[255]`   |       url or file message hash being attached to the message |
| reference  |    `STRING[255]`   |                                         post being reference |

**File Message**

The File type is used to support adding file to a name

```json
{  
	type: "FILE",  
	subtype: "TORRENT",  
	payload: {  
		name: STRING[255],  
		mimeType: STRING[255],  
		data: STRING[16777215]  
	}  
}
```

| Name     |        Type        |               Description |
| -------- | :----------------: | ------------------------: |
| name     |    `STRING[255]`   |                 File name |
| mimeType |    `STRING[255]`   |    Mime types of the file |
| data     | `STRING[16777215]` | magnetURI for the torrent |

```json
{  
	type: "FILE",  
	subtype: "IPFS",  
	payload: {  
		name: STRING[255],  
		mimeType: STRING[255],  
		data: STRING[16777215]  
	}  
}
```

| Name     |        Type        |            Description |
| -------- | :----------------: | ---------------------: |
| name     |    `STRING[255]`   |              File name |
| mimeType |    `STRING[255]`   | Mime types of the file |
| data     | `STRING[16777215]` | ipfs hash for the file |

**Moderation Message**

The MODERATION type is used to support moderation activities, such as a LIKE, UPVOTE, DOWNVOTE, BAN, etc.

```json
{  
	type: "MODERATION",  
	subtype: "LIKE" | "BLOCK",  
	payload: {  
		reference: STRING[255]  
	}  
}  
```

<table><thead><tr><th>Name</th><th align="center">Type</th><th align="right">Description</th></tr></thead><tbody><tr><td><pre><code>reference
</code></pre></td><td align="center"><code>STRING[255]</code></td><td align="right">hash of the message receiving the moderation</td></tr></tbody></table>

**Connection Message**

The CONNECTION type is used to support follows and other types of links between names.

```json
{  
	type: "CONNECTION",  
	subtype: "FOLLOW" | "BLOCK",  
	payload: {  
		name: STRING[65535]  
	}  
}  
```

| Name |       Type      |                                                              Description |
| ---- | :-------------: | -----------------------------------------------------------------------: |
| name | `STRING[65535]` | Domain names of the one being followed (e.g. bob.eth, alice.crypto, etc) |


# Simple Node

Every domain name will store data in GUN as per the following schema:

```js
const gun = Gun({
    peers: ['gun-seed-1.auti.sm/gun'],
});

gun.user().auth({
    pub: 'my-public-key',
    priv: 'my-private-key',
});

user.put({  
    message: {  
    	[messageHash]: Message  
    }  
});  
```

Anyone can run a client and observe the AutismRegister contract to get all names with a valid `pubkey` record, and then observe and store all updates from the public key associated with the identity to provide reliable data duplication for the network:

```
const contractABI = [
    {
        "anonymous": false,
        "inputs": [
            {
                "indexed": true,
                "internalType": "address",
                "name": "account",
                "type": "address"
            },
            {
                "indexed": false,
                "internalType": "bytes",
                "name": "value",
                "type": "bytes"
            },
            {
                "indexed": false,
                "internalType": "bytes",
                "name": "proof",
                "type": "bytes"
            },
            {
                "indexed": false,
                "internalType": "address",
                "name": "relayer",
                "type": "address"
            }
        ],
        "name": "RecordUpdatedFor",
        "type": "event"
    }
];

const contractAddress = "0x6b0a11F9aA5aa275f16e44e1D479A59dd00abE58";

const registrar = new new Web3().eth.Contract(
    contractABI,
    contractAddress,
);

const gun = Gun({
    peers: ['gun-seed-1.auti.sm/gun'],
});

registrar
    .getPastEvents('RecordUpdatedFor', {
        fromBlock: 2193241,
    })
    .forEach(event => {
        const pubkeyBytes = event.returnValues.value;
        const account = event.returnValues.account;
        const pubkey = Web3.utils.hexToUtf8(pubkeyBytes);
        const user = gun.user(ecdsaPubKey);
        
        user.get('message')
            .map((data, messageId) => {
                // data = Message
                // messageId = "<creator>/<messageHash>"
            });
    });
    


```

By default, we will run 2 full nodes in North America, 2 in Asia, and 2 in Europe to provide reliable global coverage. These 6 nodes will become the bootstrap peers for anyone trying to connect to Autism.


# Archive

Not Implemented

We will first integrate with IPFS and provide simple ways for creators to periodically backup all messages.

To archive messages, the creator should upload all messages to IPFS as a directory using the following format, where each `*.data` file contain a buffer of the hex-encoded message:

```
.  
├── message  
│ ├── 42740f20aed483b69701a55ab295a2ed.data  
│ └── 38650f20aed483b69701a55ab291b7f4.data  
```

After the directory is uploaded, the creator should get back an IPFS hash, of which they should add it to their GUN as below:

```
const user = Gun.user();  
user.put({  
	archive: {  
		ipfs: "CABAB_1Dt0FJsxqsu_J4TodNCbCGvtFf1Uys_3EgzOlTcg"  
	}  
});  
```

**Supporting other file protocol**

We plan to integrate with other file protocols for archives based on user demand. As long as the file protocol support directory uploads in the archive formats described above, it should be fairly trivial to integrate with such protocol.


# Eviction Policy

Not Implemented

Currently, the amount of data that a user can write to the network is unbounded, introducing a potential for sybil attack.

To mitigate such issue, in the future we will impose a data eviction policy and only expect the network's full node operator to retain the most recent 16MB of data with a valid schema.

Data for each full node is bounded by the same data eviction policy. Once a per day by default, and as often as needed, each node will go through data for each peer and remove message record based on the following rules:

* any non-archive messages with an invalid message hash
* any non-archive messages not included in the most recent 16MB (size determined by serialized message hex)
* any archive message with invalid schema

The expectation for the user is that the network will always keep the most recent 16MB of data available to participants in the network. All history beyond the most recent 16MB will be kept available based on network participation (e.g. browser peers’ local storage), as well as any available archives.


# API

https\://github.com/autism-org/indexer

**Get All Users**

`GET /users`

return list of users based on popularity (post counts + follower counts + mentioned counts) and metadata

```
curl https://api.auti.sm/v1/users?limit=1
```

```
{
    "payload":
    [
        {
            "ens": null,
            "username": "0x267eF95Cf7F536b3D7cddCDE53DedeF913C4D01D",
            "address": "0x267eF95Cf7F536b3D7cddCDE53DedeF913C4D01D",
            "joinedTx": "0xa8f2fadc8a77b63bbaac0c39aa1b0eb7f7013d60e60ed9300231af832e0281d1",
            "type": "arbitrum",
            "pubkey": "kJ--taveSOeLzItmBlgjwDU6X3SAll-FvklAqNFpcsw.FOGWEDKU2l8aeuVHbEFzFanZ_MMKC2qG4jhW5cHmHMU",
            "joinedAt": 1634475478000,
            "name": "GL10",
            "bio": "",
            "profileImage": "",
            "coverImage": "",
            "twitterVerification": "",
            "website": "",
            "meta":
            {
                "blockedCount": 0,
                "blockingCount": 0,
                "followerCount": 0,
                "followingCount": 2,
                "postingCount": 0,
                "mentionedCount": 0,
                "followed": null,
                "blocked": null
            }
        }
    ]
}
```

**Search User By String**

`GET /users/search/:query`

return list of users whose names beings with `query`

```
curl https://api.auti.sm/v1/users/search/yagami
```

```
{
    "payload":
    [
        {
            "ens": "yagamilight.eth",
            "username": "0xd44a82dD160217d46D754a03C8f841edF06EBE3c",
            "address": "0xd44a82dD160217d46D754a03C8f841edF06EBE3c",
            "joinedTx": "0xe15236b90597375392ed394a442a96aff65a18a5a9146c9ccc6da0619c81d6cd",
            "type": "arbitrum",
            "pubkey": "dBgXJATrP4KeE6zfuR4_arauMIeT_86MrQg6JbbnuxM.yJXykCW6qjB54B29by8vIWoMwk8T5NG_3awHdKC9Bgc",
            "joinedAt": 1634154376000,
            "name": "Yagami",
            "bio": "",
            "profileImage": "https://upload.wikimedia.org/wikipedia/zh/0/0c/Light_from_Death_Note.jpg",
            "coverImage": "https://images-na.ssl-images-amazon.com/images/S/sgp-catalog-images/region_GB/uomlg-JWM4ZMJATR3-Full-Image_GalleryBackground-en-US-1572961461852._SX1080_.jpg",
            "twitterVerification": "",
            "website": "",
            "meta":
            {
                "blockedCount": 0,
                "blockingCount": 0,
                "followerCount": 6,
                "followingCount": 2,
                "postingCount": 3,
                "mentionedCount": 0,
                "followed": null,
                "blocked": null
            }
        }
    ]
}
```

**Get User by ENS or Address**

`GET /users/:ensOraddress`

return a user whose address matches `ensOraddress`

```
curl https://api.auti.sm/v1/users/0xryuk.eth
```

```
{
    "payload":
    {
        "username": "0x3F425586D68616A113C29c303766DAD444167EE8",
        "address": "0x3F425586D68616A113C29c303766DAD444167EE8",
        "joinedTx": "0x106d8972b4e1e183953acc2e562a13e637e9a8139449242aae0b678a9fd5a5d8",
        "type": "arbitrum",
        "pubkey": "uVbA3TT8BS2x0WY8vpBQBtMd1aLTwFOkD6orwUVEyCQ.loloQZF5Ofcv3wExUHX97WMdRTYwidW46BnZCigf8_8",
        "joinedAt": 1634230636000,
        "name": "0xRyuk",
        "bio": "",
        "profileImage": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTJc_VJeD57pmIPHJPiPboeYzTd6JZ3CvhY-A&usqp=CAU",
        "coverImage": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSGOWvapxI_gG2fZMcaF0f2iY0KErLdwWW9Rg&usqp=CAU",
        "twitterVerification": "",
        "website": "",
        "meta":
        {
            "blockedCount": 0,
            "blockingCount": 0,
            "followerCount": 4,
            "followingCount": 3,
            "postingCount": 2,
            "mentionedCount": 3,
            "followed": null,
            "blocked": null
        },
        "ens": "0xryuk.eth"
    }
}
```

**Get All Posts**

`GET /posts`

return list of posts in descending chronological order

```
curl https://api.auti.sm/v1/posts?limit=1
```

```
{
    "payload":
    [
        {
            "type": "POST",
            "subtype": "M_POST",
            "messageId": "0x5d432ce201d2c03234e314d4703559102Ebf365C/900450baa9176d246c9199b680f6516d2c813088b4d94372d6a47a5133d1b94d",
            "hash": "900450baa9176d246c9199b680f6516d2c813088b4d94372d6a47a5133d1b94d",
            "createdAt": "1648001214812",
            "payload":
            {
                "topic": "https://twitter.com/AutismDev/status/1506452397199044608",
                "title": "",
                "content": "Few recent update to Auti.sm:\n- Add thread moderation setting for creator to moderate who can make a reply to their post\n- Add global visibility setting for creator to post to global feed or own feed only\n- Add setting ui\n- Default to blur all images",
                "reference": "",
                "attachment": ""
            },
            "meta":
            {
                "replyCount": 0,
                "likeCount": 3,
                "repostCount": 1,
                "liked": null,
                "reposted": null,
                "blocked": null,
                "interepProvider": null,
                "interepGroup": null,
                "rootId": "0x5d432ce201d2c03234e314d4703559102Ebf365C/900450baa9176d246c9199b680f6516d2c813088b4d94372d6a47a5133d1b94d",
                "moderation": "THREAD_HIDE_BLOCK",
                "modblockedctx": null,
                "modfollowedctx": null,
                "modmentionedctx": null,
                "modLikedPost": null,
                "modBlockedPost": null,
                "modBlockedUser": null,
                "modFollowerUser": null
            }
        }
    ]
}
```

**Get Post by Hash**

`GET /posts/:hash`

return a post with a specific `hash`

```
curl https://api.auti.sm/v1/post/58325b3eb288636fd2967f88651ce895a962a42d4b8f55fb1b8cce05b8a74a30
```

```
{
    "payload":
    {
        "type": "POST",
        "subtype": "",
        "messageId": "58325b3eb288636fd2967f88651ce895a962a42d4b8f55fb1b8cce05b8a74a30",
        "hash": "58325b3eb288636fd2967f88651ce895a962a42d4b8f55fb1b8cce05b8a74a30",
        "createdAt": "1647065053024",
        "payload":
        {
            "topic": "",
            "title": "",
            "content": "test",
            "reference": "",
            "attachment": ""
        },
        "meta":
        {
            "replyCount": 0,
            "likeCount": 0,
            "repostCount": 0,
            "liked": null,
            "reposted": null,
            "blocked": null,
            "interepProvider": "twitter",
            "interepGroup": "not_sufficient",
            "rootId": null,
            "moderation": null,
            "modblockedctx": null,
            "modfollowedctx": null,
            "modmentionedctx": null,
            "modLikedPost": null,
            "modBlockedPost": null,
            "modBlockedUser": null,
            "modFollowerUser": null
        }
    }
}
```

**Get All Replies**

`GET /replies?parents=:parents`

return list of replies of the `parent` post in ascending chronological order, filtered by the requester’s moderation policy

```
curl https://api.auti.sm/v1/replies?limit=20&offset=0&parent=0xc6Cb82D0199a30DC0Fc39127066CAE1815A8af55%2F5ff154c5155e47cb68d5e7ce5d7f94ba5c3a50a41a214bc1d560e7c61fd1f9fa
```

```
{
    "payload":
    [
        {
            "type": "POST",
            "subtype": "REPLY",
            "messageId": "0xd44a82dD160217d46D754a03C8f841edF06EBE3c/36674ef1f4b6a3c71456a06b36ba5c30d8d37a8b341563522a40a3866cb15c49",
            "hash": "36674ef1f4b6a3c71456a06b36ba5c30d8d37a8b341563522a40a3866cb15c49",
            "createdAt": "1639653936690",
            "payload":
            {
                "topic": "",
                "title": "",
                "content": "awesome! look forward to adding #rln functionality to #autism ",
                "reference": "0xc6Cb82D0199a30DC0Fc39127066CAE1815A8af55/5ff154c5155e47cb68d5e7ce5d7f94ba5c3a50a41a214bc1d560e7c61fd1f9fa",
                "attachment": ""
            },
            "meta":
            {
                "replyCount": 0,
                "likeCount": 0,
                "repostCount": 0,
                "liked": null,
                "reposted": null,
                "blocked": null,
                "interepProvider": null,
                "interepGroup": null,
                "rootId": null,
                "moderation": null,
                "modblockedctx": null,
                "modfollowedctx": null,
                "modmentionedctx": null,
                "modLikedPost": null,
                "modBlockedPost": null,
                "modBlockedUser": null,
                "modFollowerUser": null
            }
        }
    ]
}
```

**Get All Posts by Hashtag**

`GET /tags/:tagName`

return list of posts with the hashtag `tagName` in descending chronological order, filtered by the requester’s moderation policy

```
curl https://api.auti.sm/v1/tags/%23bugs?limit=1
```

```
{
    "payload":
    [
        {
            "type": "POST",
            "subtype": "",
            "messageId": "6c84375786fc63d708f45fea099a32c4c63b7b0f706e9b63e10128aa97f9d9f5",
            "hash": "6c84375786fc63d708f45fea099a32c4c63b7b0f706e9b63e10128aa97f9d9f5",
            "createdAt": "1647656557120",
            "payload":
            {
                "topic": "",
                "title": "",
                "content": "some #bugs\n- when replying to a moderated thread, the warning still shows up even if i can reply\n- need ability to remove users; some old anons accounts are no longer valid\n- need ways to show when anon posts has invalid proof\n- need gitdoc, man\n",
                "reference": "",
                "attachment": ""
            },
            "meta":
            {
                "replyCount": 3,
                "likeCount": 2,
                "repostCount": 0,
                "liked": "/4de30a7dabd3a1779b0ef0da9de906667d0cc066d15e80a95adcd460afd004cf",
                "reposted": null,
                "blocked": null,
                "interepProvider": "twitter",
                "interepGroup": "not_sufficient",
                "rootId": "6c84375786fc63d708f45fea099a32c4c63b7b0f706e9b63e10128aa97f9d9f5",
                "moderation": null,
                "modblockedctx": null,
                "modfollowedctx": null,
                "modmentionedctx": null,
                "modLikedPost": "/4de30a7dabd3a1779b0ef0da9de906667d0cc066d15e80a95adcd460afd004cf",
                "modBlockedPost": null,
                "modBlockedUser": null,
                "modFollowerUser": null
            }
        }
    ]
}
```

**Get All Hashtags based on Post Counts**

`GET /tags`

return list of existing hashtags in descending order based on total post counts

```
curl https://api.auti.sm/v1/tags?limit=3
```

```
{
    "payload":
    [
        {
            "tagName": "#bugs",
            "postCount": 4
        },
        {
            "tagName": "#bug",
            "postCount": 4
        },
        {
            "tagName": "#autism",
            "postCount": 3
        }
    ]
}
```

**Get Homefeed of a user**

`GET /homefeed`

return list of posts in descending chronological order, based on the requester’s followers and at-mentioned

```
curl -XGET -H 'x-contextual-name: 0xd44a82dD160217d46D754a03C8f841edF06EBE3c' 'https://api.auti.sm/v1/homefeed?limit=1'
```

```
{
    "payload":
    [
        {
            "type": "POST",
            "subtype": "REPOST",
            "messageId": "0x3F425586D68616A113C29c303766DAD444167EE8/83f47485e5bfa00d3b11206433acade0589bf4e2039ed7ccfef91aaa8889de54",
            "hash": "83f47485e5bfa00d3b11206433acade0589bf4e2039ed7ccfef91aaa8889de54",
            "createdAt": "1648007814847",
            "payload":
            {
                "topic": "",
                "title": "",
                "content": "",
                "reference": "0x5d432ce201d2c03234e314d4703559102Ebf365C/900450baa9176d246c9199b680f6516d2c813088b4d94372d6a47a5133d1b94d",
                "attachment": ""
            },
            "meta":
            {
                "replyCount": 0,
                "likeCount": 3,
                "repostCount": 1,
                "liked": "0xd44a82dD160217d46D754a03C8f841edF06EBE3c/8d778ad9d1902a6b2250634ec0ff95e7c145c3f8394236b2f0ae5be74450cf5e",
                "reposted": null,
                "blocked": null,
                "interepProvider": null,
                "interepGroup": null,
                "rootId": "0x5d432ce201d2c03234e314d4703559102Ebf365C/900450baa9176d246c9199b680f6516d2c813088b4d94372d6a47a5133d1b94d",
                "moderation": "THREAD_HIDE_BLOCK",
                "modblockedctx": null,
                "modfollowedctx": null,
                "modmentionedctx": null,
                "modLikedPost": null,
                "modBlockedPost": null,
                "modBlockedUser": null,
                "modFollowerUser": null
            }
        }
    ]
}
```

**Get Notifications of a user**

`GET /:address/notifications`

return list of notifications relevant to `:address`

```
curl -XGET -H 'x-contextual-name: 0xd44a82dD160217d46D754a03C8f841edF06EBE3c' 'https://api.zkitter.com/v1/0xd44a82dD160217d46D754a03C8f841edF06EBE3c/notifications?limit=20&offset=0'
```

```
{
    "payload":
    [
        {
            "message_id": "0xd44a82dD160217d46D754a03C8f841edF06EBE3c/36674ef1f4b6a3c71456a06b36ba5c30d8d37a8b341563522a40a3866cb15c49",
            "type": "LIKE",
            "timestamp": "1668534603794",
            "creator": "0x6ae8EA3D4027DFbfdBC9B2e92F2c3D24997d1faa",
            "sender_pubkey": null
        },
        {
            "message_id": "0xf622d6eC8a21532a62BA2CAFdda571c24D670E5c/fe31962c2acfc2667ec9c7e40d5217990e5514b19efef137226cb6135667a74f",
            "type": "REPLY",
            "timestamp": "1649288251572",
            "creator": "0xf622d6eC8a21532a62BA2CAFdda571c24D670E5c",
            "sender_pubkey": null
        },
        {
            "message_id": "35acc3ce433eb71fe4074475f6b1a7c3f18657140c7912f7cf68339aac63b8b3",
            "type": "REPLY",
            "timestamp": "1647656226942",
            "creator": "",
            "sender_pubkey": null
        },
        {
            "message_id": "0x3F425586D68616A113C29c303766DAD444167EE8/15b338b875a4ef5493e41c5fb4c80b6055fc7a5c4470fd4df826088b109e843b",
            "type": "REPLY",
            "timestamp": "1647656027881",
            "creator": "0x3F425586D68616A113C29c303766DAD444167EE8",
            "sender_pubkey": null
        },
        {
            "message_id": "0xf622d6eC8a21532a62BA2CAFdda571c24D670E5c/2560501202b9fb3c7acb659cad4ccddc0c409f37f7a14f10b0d17ae19fb895ad",
            "type": "REPLY",
            "timestamp": "1647655922998",
            "creator": "0xf622d6eC8a21532a62BA2CAFdda571c24D670E5c",
            "sender_pubkey": null
        },
        {
            "message_id": "0x76De5612eD1F97C1042D56f1Dd3ae803045BC58e/637ca4c43d4774d0cf64f80e327c05f1ed2aa9e8f3d39ef2f8223406bab57161",
            "type": "REPLY",
            "timestamp": "1637697489077",
            "creator": "0x76De5612eD1F97C1042D56f1Dd3ae803045BC58e",
            "sender_pubkey": null
        },
        {
            "message_id": "0xd44a82dD160217d46D754a03C8f841edF06EBE3c/4f783b9e43a2e981c88f2871b733235ac29009c91ef1d545bea111b76ebef4fd",
            "type": "REPLY",
            "timestamp": "1637664976039",
            "creator": "0xd44a82dD160217d46D754a03C8f841edF06EBE3c",
            "sender_pubkey": null
        },
        {
            "message_id": "0xd44a82dD160217d46D754a03C8f841edF06EBE3c/c52a8401ead34a3be74946f816d21712913f0f254764745168272b92c48c280a",
            "type": "REPLY",
            "timestamp": "1637493383569",
            "creator": "0xd44a82dD160217d46D754a03C8f841edF06EBE3c",
            "sender_pubkey": null
        },
        {
            "message_id": "0xd44a82dD160217d46D754a03C8f841edF06EBE3c/197986697ade69cf40d8192a5e477a7049cf6823ef7cce292254330b69013e09",
            "type": "LIKE",
            "timestamp": "1637364544736",
            "creator": "0x76De5612eD1F97C1042D56f1Dd3ae803045BC58e",
            "sender_pubkey": null
        },
        {
            "message_id": "0xd44a82dD160217d46D754a03C8f841edF06EBE3c/1d3bceca9d204ae786a65e6c6a745560ab718d901a7413660ce37f1d72e95c36",
            "type": "REPLY",
            "timestamp": "1637315663019",
            "creator": "0xd44a82dD160217d46D754a03C8f841edF06EBE3c",
            "sender_pubkey": null
        },
        {
            "message_id": "0xd44a82dD160217d46D754a03C8f841edF06EBE3c/197986697ade69cf40d8192a5e477a7049cf6823ef7cce292254330b69013e09",
            "type": "LIKE",
            "timestamp": "1637315641901",
            "creator": "0xd44a82dD160217d46D754a03C8f841edF06EBE3c",
            "sender_pubkey": null
        }
    ]
}
```


# Libraries

Coming Soon!

**NPM**

zkchat - a library for building end-to-end encrypted chat using RLN zero-knowledge proof

zksocial - a library for syncing and querying social messages over a peer-to-peer network

[zkitter-js](https://www.npmjs.com/package/zkitter-js) - JS implementation of Zkitter


# zkitter-js

Javascript implementation of a standalone zkitter node

[zkitter-js](https://github.com/zkitter/zkitter-js) is an npm module and a CLI tool designed to make building on Zkitter easier.

#### To initialize Zkitter and sync with the network:

<pre class="language-typescript"><code class="lang-typescript"><strong>import {Zkitter} from "zkitter-js";
</strong>const zkitter = await Zkitter.initialize({
  arbitrumHttpProvider: 'https://...',
});

// Sync with arbitrum registrar
await zkitter.syncUsers();

// Sync with zk groups on zkitter
await zkitter.syncGroup();

// Get all historical messages (30 days) from Waku store
await zkitter.queryAll();

// Subscribe to all future messages from everyone
await zkitter.subscribe();
</code></pre>

#### To implement custom database instead of using default LevelDB:

```typescript
import { Zkitter, GenericDBAdapterInterface, Post, Proof } from 'zkitter-js';
import postgres from 'postgres';

const sql = postgres({ /* options */ });

class PostgresDB implements GenericDBAdapterInterface {
    async insertPost(post: Post, proof: Proof) {
        const existing = await sql`
            select * from posts
            where hash = ${post.hash()}
        `
        
        if (!existing) {
            await sql`
                insert into posts (...)
                values (...)
            `
        }
    }
}

const zkitter = await Zkitter.initialize({
  db: new PostgresDB(),
  arbitrumHttpProvider: 'https://...',
});
```

```typescript
interface GenericDBAdapterInterface {
  getUserCount: () => Promise<number>;
  getLastArbitrumBlockScanned: () => Promise<number>;
  updateLastArbitrumBlockScanned: (block: number) => Promise<number>;
  updateUser: (user: User) => Promise<User>;
  getUsers: (limit?: number, offset?: number|string) => Promise<User[]>;
  getUser: (address: string) => Promise<User|null>;
  getUserMeta: (address: string) => Promise<UserMeta>;
  getProof: (hash: string) => Promise<Proof | null>;
  insertGroupMember: (groupId: string, member: GroupMember) => Promise<GroupMember|null>;
  getGroupMembers: (groupId: string, limit?: number, offset?: number|string) => Promise<string[]>
  findGroupHash: (hash: string) => Promise<string | null>;
  insertPost: (post: Post, proof: Proof) => Promise<Post>;
  insertModeration: (moderation: Moderation, proof: Proof) => Promise<Moderation|null>;
  insertConnection: (connection: Connection, proof: Proof) => Promise<Connection|null>;
  insertProfile: (profile: Profile, proof: Proof) => Promise<Profile|null>;
  getMessagesByUser: (address: string, limit?: number, offset?: number|string) => Promise<Message[]>;
  getPostMeta: (postHash: string) => Promise<PostMeta>;
  getPost: (hash: string) => Promise<Post|null>;
  getPosts: (limit?: number, offset?: number|string) => Promise<Post[]>;
  getUserPosts: (address: string, limit?: number, offset?: number|string) => Promise<Post[]>;
  getReplies: (hash: string, limit?: number, offset?: number|string) => Promise<Post[]>;
  getReposts: (hash: string, limit?: number, offset?: number|string) => Promise<string[]>;
  getModerations: (hash: string, limit?: number, offset?: number|string) => Promise<Moderation[]>;
  getConnections: (address: string, limit?: number, offset?: number|string) => Promise<Connection[]>;
}
```

#### CLI Usage:

You must first initialize zkitter cli with an HTTP provider for Arbitrum mainnet

<pre class="language-shell"><code class="lang-shell"><strong>zkitter init --arbitrumHttpProvider="https://..."
</strong></code></pre>

**To fetch all users from Arbitrum:**

```
zkitter sync -a
```

**To sync with one group or all groups:**

```
zkitter sync -g semaphore_taz_members
zkitter sync --groups
```

**To list all groups or users:**

```
zkitter list -u
zkitter list -g
```

**To publish a post:**\
(instruction to generate private key is not available yet!)

```
zkitter write -c "Hello, World!" -u "0x12345..." -s "base64-encoded-private-key"
```

**To view a user profile:**

```
zkitter whois 0xd44a82dD160217d46D754a03C8f841edF06EBE3c
```

<figure><img src="/files/y3Fk0UtQikRFsUCb9KDr" alt=""><figcaption></figcaption></figure>

**To view timeline:**

```
zkitter timeline --limit=3
```

<figure><img src="/files/T2gA8QprtEgG1ShYAGxu" alt=""><figcaption></figcaption></figure>

**To run zkitter node:**

```
zkitter up
# type "help" view command options
```


# How to sign up with Metamask?

1. Click "Add a user" on top-right corner

![](/files/iCy6XMZxKQoE59IibJaj)

2\. Connect to Metamask

![](/files/GE6yVAb20qH36DMJMG3Q)

3\. Select "Wallet Address", then click "Next"

![](/files/wY24AHIlP4hrNpAYJxzc)

4\. Sign a message to create identity&#x20;

![](/files/inINpky0uu0tf9QYToxq)

5\. Sign another message to update your identity

![](/files/kKz1orP73F7dd93L7xmK)

6\. Wait for the transaction to finish. You will be automatically redirect

![](/files/yDtI2k2IwNHtYqg8xuoR)

7\. Fill out your profile

![](/files/8rpOA4k6EEMVZDjpnvqQ)

8\. Create a password to backup your identity

![](/files/jxE6DDLIAJpJKIodrQtq)

9\. You finished signup!

![](/files/OHGWLwE9vJMClcRZNhJz)


# How to chat anonymously?

ZK Social allows users to chat anonymously as a member of the following groups:\
\- Any users from ZK Social\
\- Any users from [Interep Twitter Groups](https://docs.interep.link/technical-reference/reputation/twitter)

### Chat as any users

1. Select *Chat anonymously* when creating a new chat. Each anonymous chat session are independent - exposing identity from one conversation will not leak your identity from other anon conversations

![](/files/jzNuWFL03XgxWM6ST61F)

2\. You can chat as your normally would in the anon conversation. Note that the *sender* icon is turned into an incognito icon instead of your profile picture. An anon conversation is also slightly *darker* than other conversations.

![](/files/7wbjwe9PIdWQ69ozs8tE)

### Chat as a member of Interep group

To chat as a member of interep groups, a user must first add an anonymous identity following [this guide](/faqs/how-to-create-an-anonymous-user). Note that the *sender* icon should change to the icon of the corresponding interep groups.

![](/files/241xJ5XRII8tDuKmQPWI)


# What is an Anonymous account?

![Screenshot of an anonymous post](/files/koPhjdpBvIL04YsGdfq2)

Instead of posting under a wallet address, users can post anonymously by creating an anonymous user using [Interep](https://docs.interep.link/). Anonymous users can only make post and comments on others posts, but they cannot like, block, or follow other users.

### &#x20;Reputation Groups

In order to create an anonymous user, a user must create an identity commitment and meet minimum reputation requirement for one of the following [Interep groups](https://docs.interep.link/technical-reference/reputation/twitter):

* *Twitter Gold*: at least 7000 followers AND verified profile AND a max [botometer ](https://botometer.osome.iu.edu/faq#which-score)of 1
* *Twitter Silver*: at least 2000 followers AND a max botometer of 1.5
* *Twitter Bronze*: at least 500 followers AND a max botometer of 2
* *Twitter Any*: any twitter profiles

All anonymous posts are ranked by reputation in global feed, and users can filter by minimum reputation.&#x20;


# How to create an anonymous user?

1. Select **Metamask**, then click **Next**

![](/files/h3qxt0DE8W2qKuq4FeiQ)

2\. Select **Anonymous**, then click **Next**

![](/files/7Tm2Kx95pP4Zk9BX0BrD)

3\. Click **Twitter**

![](/files/xivZPWaTMRvlW9NFMdy8)

4\. Authorize **Autism** to access your profile

![](/files/OlBMvl6SX48xjra6WlQc)

5\. Click **Create Identity** to sign a message in Metamask mask and generate your Semaphore ID

![](/files/VwR4m407wT1O4Gcz7L4v)

6\. Sign in **Metamask** - be sure to verify that the origin is *<https://www.auti.sm> or <https://auti.sm>*

![](/files/s2AqfUKqq2ESLwyJeJYk)

7\. Click **Join Group**

![](/files/Z0Aq78LX2KZNdeziO8yI)

8\. You have created an anonymous user! :)

![](/files/QXb2Pt8845PGOPctpFmh)


# How to create a custom group?

1. Check the box **This is a group profile** when editing your profile

<figure><img src="/files/yigoQdN1EHpptcseC6Mr" alt=""><figcaption></figcaption></figure>

2\. Click on **Invite members** and search for the users you want to invite

<figure><img src="/files/2XUmZgfMMOvZ34XZcfsK" alt=""><figcaption></figcaption></figure>

3\. The invited user will received a notification and can accept the invite on your profile

<figure><img src="/files/1zerdLByPORyFNYgCqoq" alt=""><figcaption></figcaption></figure>

4\. Once invites are accepted, members can post as a member of the group without leaking the identity of the member

<figure><img src="/files/pD8JSdoY5vSEBKNH0Iyp" alt=""><figcaption></figcaption></figure>


# How does moderation work?

### Thread Moderation

Users can moderate their own thread by adjusting who can reply to their post. By default, a post is *unmoderated* and anyone can reply. To change moderation setting, click on **Show reply from everyone**

![](/files/tJAZRhY5XXpCq77CP22A)

You should see four different moderation settings:

* **Show reply from everyone**: anyone can comments on your post
* **Hide replies that you blocked**: users who are blocked by OP cannot reply. If OP blocks a specific reply, such reply will be hidden away from everyone's default views
* **Show replies that you followed or liked**: only users followed by OP can make a reply. If OP likes a specific reply when reviewing unmoderated view, such reply will appear on everyone's default views
* **Show replies from people you mentioned**: only users mentioned on the original post can reply to the thread. OP can also show/hide reply from default views by liking or blocking specific replies.

Thread moderations are only applied locally. Any users can choose to ignore the OP's moderation policy by opting into an unmoderated view.

![](/files/LRaWwh5utNs2u5wPIrW4) ![](/files/0Gzz9SfLamV15npVPwMl)

### Global Moderation

At the node level, each [autismd](https://github.com/autism-org/autismd) node can filter out contents from API response by setting a list of addresses, representing the list of global moderators whose combined list of blocked users will become the banlist for the node.

This allows node operators to apply any moderation policy as they choose without affect other nodes in the network.


# What shows up in the global feed?

The global feeds shows all public posts from all users. By default, all posts are NOT posted to the global feed. In order to make the post discoverable globally, the user should check the box **Post to global timeline** using the post menu.

![](/files/yQ5ocByjUIq4dBV8hULz)


# How to upload an image?

Each image shall not exceed 5MB. All images are uploaded and pinned to IPFS.&#x20;

Users can obtain the IPFS hash of any images from ZK Social and add their own pins if necessary. Support for user's to add their own pinning service will be added in the future, which would allow users to use their own storage space for different types of files.

1. From the post editor, click on the image icon

![](/files/YJr3vQoasYzVAl8Pc7Ya)

2\. Click the **Select File** button, or drag & drop the image inside the dotted area

![](/files/dahKTlzCKqmSgP6GyaTD)

3\. Alternatively, you can paste a link to your image

![](/files/vnGAexkrmJl8yDmYlbbw)

4\. If the file or link is valid, a preview will be generated

![](/files/9h88Sq0DlfMNBYQvPosR)

5\. Finish your post

![](/files/nPnPnXe2Td7YrzdYb1Mg)


# How to share a WebTorrent seed?

1. To share a webtorrent seed, you must first obtain a magnet URL from a [torrent client with WebRTC peers](https://github.com/webtorrent/webtorrent/issues/369). If you are uploading and seeding for personal purpose, I would recommend using [WebTorrent Desktop](https://webtorrent.io/desktop/)

![](/files/qhcVkryJjv1Uhzs0FtKu)

2\. Paste the magnet link to the URL input box, then click on the *binocular* icon

![](/files/XWMnxu347cZrVl0OCkp0)

3\. If the magnet link is valid, a preview box should show up, showing the files inside the torrent

![](/files/oaECF68PPSEQgB4edo6c)

4\. Other users will be able to download and view the torrent directly from the UI

![](/files/eOJzN5KR0W6Xr4Ou7Yj9)

![](/files/qUpe2eEazPdGaeuwh4j2)


# Links

**What is the official website?**\
<https://auti.sm>

**Where can I find the repositories?**\
<https://github.com/autism-org>

**Autism**\
<https://www.auti.sm/0x5d432ce201d2c03234e314d4703559102Ebf365C/>

**Twitter**\
<https://twitter.com/AutismDev>

**Discord**\
<https://discord.com/invite/GVP9MghwXc><br>


