Developer Hub
Relation ProtocolRelation ONE APIRelation Graph API
English
English
  • Overview
    • RelationONE
  • Developer Guide
    • Glossary
    • Service Address
    • Rate Limits
    • JS-SDK
    • Quick Start
  • Introducing Relation ONE IM
  • API
    • Introduction
    • Signature
    • Auth
    • Profile
      • Bind Address
      • Unbind Address
      • User Info
      • List Address
      • Web2 Account
    • Follows
      • Follow
      • Unfollow
      • Follower
      • Following
      • Web3 Follower
      • Web3 Following
    • Recommendation
    • Groups
      • Create
      • Join
      • Remove Members
      • Leave
      • Disband
      • Transfer Owner
      • Info
      • Members
      • Is Admin
      • Get Announcement
      • Update Announcement
    • Chats
      • Pin
      • Mute
      • List
      • Hide
    • Message
      • Send
      • Read
      • Hide
      • List
      • Unread Count
  • JS-SDK
    • Relation-Auth
      • Quick Start
      • Method
    • IM-JS-SDK
      • Quick Start
      • Static Method
      • Events
      • Method
      • Parse Message
    • Plugin-JS-SDK
      • Quick Start
  • Appendix
    • Error Code
    • Contract Address
Powered by GitBook
On this page
  • 1. The main procedure for sending messages
  • 2. Identity
  • 2.1. Relation Name Service
  • 3. Client
  • 4. Proxy
  • 4.1. Receive/Send Messages
  • 4.2. State Storage
  • 4.3. Message Persistence
  • 4.4. The REST interface
  • 5. Manage relationships
  • 5.1. Manage contacts
  • 5.2. Manage groups
  • 6. Chat
  • 6.1. P2P chat
  • 6.2. Group chat
  • 6.3. Encrypt/Decrypt
  • 7. Define a message
  • 7.1. Message structure
  • 7.2. Messages
  • 7.3. Payload
  • 7.4. The structure of a Chat
  • 8. Message storage
  • 9. Contract

Introducing Relation ONE IM

PreviousQuick StartNextIntroduction

Last updated 2 years ago

Relation ONE IM is a decentralized private messaging protocol.

  • It implements , manages identity via , and manages contacts and groups with .

  • It uses to encrypt and decrypt messages.

  • It uses as its decentralized storage solution.

Relation ONE IM realizes true decentralization and privacy.

It consists of four parts: (see Fig. 1-1)

  • Client: This is a front-end application for users to encrypt and decrypt messages and to send such messages via Proxy. It uses the standard interface provided by Relation Protocol to manage contacts and groups.

  • Proxy: This is a simple back-end program that accepts messages from "Client" only. It stores the messages to Arweave and pushes them to their receivers.

  • Decentralized Storage(Arweave): To store encrypted messages permanently.

  • Contracts: A set of contracts (Relation Name Service, Follow, DAO) conforming to Relation Protocol to store user information, Follow relationships, and information on DAOs.

1. The main procedure for sending messages

See Fig. 1-1

  1. A sender connects to "Client" using a wallet.

  2. "Client" sends the encrypted message to a "Proxy".

  3. The "Proxy" stores the message permanently.

  4. The "Proxy" sends the message to the receiver's "Client" via Websocket.

  5. The receiver decrypts the message on the "Client" using Lit Protocol.

2. Identity

Each pair of Ethereum public and private keys constitutes an identity. Users can use wallets like MetaMask to connect to "Client".

An ethereum address is like an ID for a user. When messaging a user, the message body specifies the receiver's address, and the "Proxy" will push the message to the "Client" logged in using the target address.

The private key signs the message body to ensure the message is sent by that address and cannot be modified by anybody.

The identity needs to register a SBT Token in [Relation Name Service](#2.1. Relation Name Service) as a prerequisite for private messaging.

2.1. Relation Name Service

Before a user can use private messaging, a SBT Token shall be acquired by the user via registering with Relation Name Service contract.

In a chat, we can see other users' information ,such as Name and Avatar, from Relation Name Service.

Relation Name Service contract provides the following interfaces:

  • register: register(address owner, string calldata name, bool reverseRecord) external returns (uint)

  • set an avatar: setProfileURI(string memory profileURI) external

  • Query a user name: nameOf(address addr) external view returns (string memory)

  • Query an avatar: profileURI(address addr) external view returns (string memory)

3. Client

When users are logged in their "Client"s, the "Client" will connect to Proxy via WebSocket. When someone sends messages to said users, the Proxy will push the messages to "Client", and the "Client" can decrypt the messages using Lit Protocol.

Before sending messages, the "Client" composites Messages according to the [Messages structure](#7.1. Message structure) , encrypts the Messages using Lit. Finally, the encrypted Messages are sent to Proxy.

Data privacy is secured this way because messages are encrypted and decrypted on the "Client" side.

The "Client" also manages [contacts](#5.1. Manage contacts) and [groups](#5.2. Manage groups) by calling respective contracts.

4. Proxy

Relation ONE Proxy is a back-end application which provides a set of REST API (to receive messages from "Client" and to modify states) and a WebSocket interface (to push messages to "Client".)

"Proxy" will store the messages sent from "Client" to Arweave. Because the messages are stored in a decentralized manner, "Client" does not rely on any single "Proxy". Even if one "Proxy" fails, "Client" can change to another "Proxy" with the message data intact.

4.1. Receive/Send Messages

Client sends encrypted messages to Proxy via REST API. The Proxy will store the messages to Arweave according to the Message Persistence strategies and push the messages to the Client logged in by the receiver.

4.2. State Storage

Data concerning states, such as unread messages, muted group messages, sticky messages, are stored by a "Proxy" in its own database. These data are not shared among different Proxies when a Client switches between them.

4.3. Message Persistence

[Proxy](#4. Proxy) uploads [Messages](#7.2. Messages) containing one or multiple [Message(s)](#7.3. Payload). When uploading to Arweave, each Transaction has a tag pointing to the Hash of the previous Transaction.

There are three strategies for Message Persistence:

  1. Pack and upload the messages to Arweave every 12 hours.

  2. If there is no message within 12 hours, then the pack will not be conducted, and the count for "12 hours" restarts.

  3. When the messages exceeded 100MB in size, they will be packed and uploaded to Arweave even if the scheduled task is not yet triggered.

4.4. The REST interface

A "Proxy" should provide the following interfaces:

Description
Http Method
interface
Request Body
Response Body

Send messages

POST

/message/send

Query messages

GET

/message/list?limit=${limit}&offset=${offset}

Mark as read

POST

/message/read?messageId=${messageId}

Unread message count

GET

/message/unreadCount

Long

Chat lists

GET

/chats/list?limit=${limit}&offset=${offset}

5. Manage relationships

5.1. Manage contacts

Each [identity](#2. Identity) creates a Follow contract using FollowRegister (see Fig. 1-4). When Alice follow Bob, the former needs to call the follow() method of Bob's Follow contract .

  • Friend list: List all Follow contracts with FollowRegister and list each user's follow information.

  • Follow: call the follow() method from the other person's contract.

  • Unfollow: call the unfollow() method from the other person's contract.

5.2. Manage groups

Example: A creator creates a DAO contract via DaoRegister. The isFreeJoin() method is used to set whether people are free to join the group. With the join() method, one can join a group chat. With the remove(address addr) method, one can leave a group chat. (see Fig. 1-5)

  • Create a group chat: The Client calls the deployDaoContract(address owner,string memory name) external returns (uint256) method of the DaoRegister contract to create a new group chat.(See Fig. 1-5).

  • Join a group chat: If the isFreeJoin() is set to True, then one can use the join() method to freely join a group. It is set to False, then the administrator needs to add members via the method addMember(address[] memory addr) external.

  • Leave a group chat : One can call the method remove(address addr) to leave a group chat. Or, the administrator can call the method remove(address addr) to remove a member.

  • Set a group announcement: The administrator of a group sends a special [message](#7.1. Message structure) in the group to realize this, with the kind set to 3.

  • Group name: Query a group's name via the method name() .

  • Group avatar: Query the group avatar using the method daoURI().

  • Group members: The addresses listed in the contract holding its SBT Tokens are group members.

6. Chat

6.1. P2P chat

Users can chat with their [contacts](#5.1. Manage contacts) or send messages to Ethereum addresses not owned by their contacts.

6.2. Group chat

6.3. Encrypt/Decrypt

As with Fig. 1-6, Alice wants to send an encrypted message to Bob: Hi.

  1. Alice's "Client" will use the Lit SDK to encrypt Hi locally. Subsequently, it generates the encrypted message encryptedString and key symmetricKey:

const messageToEncrypt = "Hi";
// 1. Encryption
const { encryptedString, symmetricKey } = await LitJsSdk.encryptString(messageToEncrypt);
  1. Alice's "Client" sets accessControlConditions to be visible to Bob's address, and uploads accessControlConditions and key symmetricKey to Lit's node.

const accessControlConditions = [
    {
      "contractAddress": "",
      "standardContractType": "",
      "chain": "ethereum",
      "method": "",
      "parameters": [
        ":userAddress",
      ],
      "returnValueTest": {
        "comparator": "=",
        //Bob's wallet address
        "value": "0x50e2dac5e78B5905CB09495547452cEE64426db2"
      }
    }
];
// 2. Saving the Encrypted Content to the Lit Nodes
const encryptedSymmetricKey = await litNodeClient.saveEncryptionKey({
  accessControlConditions,
  symmetricKey,
  authSig,
  chain,
});
  1. Alice's "Client" composites Message and send it to "Proxy".

const message = {
  ...
  "sender": "Alice's wallet address",
  "receiver": "Bob's wallet address",
  "payload": "${encryptedString}",
  "encryptedSymmetricKey": "${encryptedSymmetricKey}",
  ...
}
  1. The Proxy forwards the message to Bob's "Client".

  2. After Bob's "Client" received Message pushed by the "Proxy", it acquires the key _symmetricKey after it passed the verification conducted by the Lit node:

// 5. Decrypt it
// <String> toDecrypt
const toDecrypt = LitJsSdk.uint8arrayToString(encryptedSymmetricKey, 'base16');

// <Uint8Array(32)> _symmetricKey
const _symmetricKey = await litNodeClient.getEncryptionKey({
    accessControlConditions,
    toDecrypt,
    chain,
    authSig
})
  1. Bob decrypts the message locally:

// <String> decryptedString
let decryptedString;

try{
    // gets the original text: Hi
    decryptedString = await LitJsSdk.decryptString(
        encryptedString,
        _symmetricKey
    );
}catch(e){
    console.log(e);
}

7. Define a message

7.1. Message structure

{
  "id": "${messageId}",
  "encryptedBy": "Lit",
  "sender": "${senderWalletAddress}",
  "receiver": "${receiverWalletAddress/receiverDaoContractAddress}",
  "timestamp": ${the message creates timestamp in milliseconds},
  "kind": ${kind},
  "tags": [
    ["quoteId", "${messageId of the quoted message}"],
    ["mentionedUsers", "${The wallet address of the mentioned user}"]
  ],
  "type": "${TEXT/CARD/NFT/IMAGE/EMOJI/BATCH_TRANSFER/ANNOUNCEMENT}",
  "payload": "${message encrypted by Lit}",
  "encryptedSymmetricKey": "${a hex string that LIT will use to decrypt payload as long as you satisfy the conditions}",
  "delegationMessage": "Authorize  ${address} to delegate signature, Creation timestamp: ${}, Expiry timestamp: ${}",
  "delegationSig": "${The signature generated by signing against ${delegationMessage} using private key}",
  "sig": "${The signature generated by signing against ${messageId} using private key}"
}
  • How to generate messageId : Composite a json string with the following format and calculate its Keccak256:

[
  <encryptedBy>,
  <sender>,
  <receiver>,
  <timestamp>,
  <kind>,
  <tags>,
  <payload>,
  <delegationMessage>,
  <delegationSig>
]
  • kind

    • 0: p2p message。

    • 1: group message。

    • 2: dao message。

    • 3: announcement。

    • 4: hide message。

  • Session Key:

A pair of public and private keys are generated on the Client side as the Session Key. The Session Key is used to composite delegationMessage. Then, the user's private key will be used to sign against delegationMessage, resulting in delegationSig.

When users send a message, they sign against the message using the private key of the Session Key pair, and attach the delegationMessage and delegationSig. With this delegationChain, it can be verified that the message is indeed signed by the said user.

  • How to generate sig:

Sign messageId using the private key of Session Key, and you will have a sig.

7.2. Messages

"Messages" is an array with one or multiple "Message" with the following format:

Messages = [Message,Message,...]

7.3. Payload

  • Text message

{
  //  ...
  "type": "TEXT",
  "payload": "Hi"
  //  ...
}
  • CARD message

{
  //  ...
  "type": "CARD",
  "payload": "type:href;action:shareFavorite;p1(Text):https%3A%2F%2Fspace.id%2Fvoyage,p2(Text):,p3(Text):SPACE ID,p4(Text):https%3A%2F%2Fspace.id%2Ffavicon.ico"
  //type:${href};action:${shareFavorite};p1(Text):${URL},p2(Text):,p3(Text):${title},p4(Text):${icon}
  //  ...
}
  • NFT

{
  //  ...
  "type": "NFT",
  "payload": "type:image;action:nftImage;p1(Text):https%3A%2F%2F3fypb-gqaaa-aaaag-aaedq-cai.ic1.io%2Fnft%2Fpolygon%2F0x4b5c095380e8e5f016a70a28af570c3bca93b811%2F25908.jpeg,p2(Text):polygon"
  //type:image;action:nftImage;p1(Text):${imageURL},p2(Text):${chainName}
  //  ...
}
  • Local IMAGE

{
  //  ...
  "type": "IMAGE",
  "payload": "type:image;action:localImage;p1(Text):https%3A%2F%2F3fypb-gqaaa-aaaag-aaedq-cai.ic1.io%2Fim%2Fimage%2Fnymb5-kqaaa-aaaaj-4thiq-cai%2F2f3537d1a2474f2fb92cb5f7de2ed70a.png,p2(Text):im/image/nymb5-kqaaa-aaaaj-4thiq-cai/2f3537d1a2474f2fb92cb5f7de2ed70a.png"
  //type:image;action:localImage;p1(Text):${imageURL},p2(Text):${s3ObjectKey}
  //  ...
}
  • EMOJI

{
  //  ...
  "type": "EMOJI",
  "payload": "type:image;action:customEmoji;p1(Text):https%3A%2F%2F3fypb-gqaaa-aaaag-aaedq-cai.ic1.io%2Fim%2Femoji%2Flov6e-qqaaa-aaaaj-xykxq-cai%2Fac1dc2bf6bad48aeb1a502c0a28ee942.jpg,p2(Text):im/emoji/lov6e-qqaaa-aaaaj-xykxq-cai/ac1dc2bf6bad48aeb1a502c0a28ee942.jpg"
  //type:image;action:customEmoji;p1(Text):${imageURL},p2(Text):${s3ObjectKey}
  //  ...
}
  • Batch transfer

{
  //  ...
  "type": "BATCH_TRANSFER",
  "payload": "0xd84ed2b4deacbad8a568747fea45f3fc7f2d204595101f99f43006712f3ff290::USDT::20.0000::0x969f85053b44d7eCE8108094420Aba45149b7A3a::qzoo5-ryaaa-aaaaj-xs6ma-cai"
  //payload: 0xbaf915e778b044dbf62974d17bd2ab8875bccce8bd02c40dfc241d2893755851::USDT::5.0000::aukee-3yaaa-aaaah-qc3ya-cai::4xtgo-viaaa-aaaaj-aavyq-cai,ms5ob-iqaaa-aaaaj-aa2za-cai,kppoo-ozrgz-rtkn3-cgqyq-cai,meohx-szymu-ytezr-ugiza-cai,oazqd-ndeg4-ytqyj-zga2q-cai
  //payload: 0x71de31f73f34fc9af20f7ce3b35ec4e56bf25af476f99cb376779761c2fe91fa::USDT::1.0000::aukee-3yaaa-aaaah-qc3ya-cai::7zshb-ryaaa-aaaaj-aag3a-cai
  //  ...
}
  • ANNOUNCEMENT

{
  //  ...
  "type": "TEXT",
  "payload": "It's an announcement"
  //  ...
}

7.4. The structure of a Chat

{
  "id": "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5",
  "name": "test-group",
  "avatar": "",
  "channelType": "GROUP",
  "unreadMessageCount": 0,
  "type": "TEXT",
  "payload": "the message payload",
  "lastPostAt": "1665219499000"
}

8. Message storage

The application uses Arweave to store encrypted messages permanently to ensure reliability and decentralization.

A series of [Message](#7.1. Message structure) is grouped into [Messages](#7.2. Messages) by [Proxy](#4. Proxy) and uploaded to Arweave. Each Transaction has a tag pointing to the Hash of the previous Transaction, so it is easy to find all history messages with the latest Hash.

Messages = [Message, Message, Message]

9. Contract

Name
Docs
Mumbai
Goerli
zkSync Era Testnet

Relation Name Service

0x04C23dDfE813d8D208bc8120b73F8EF30f423850

0xe4AA1f1E5be0A4E7F80CAC26C2Db1611C3E70c41

-

FollowRegister

0xA0cECa90d7A2033f5162dc73391cdB72272456E7

-

0x734dE4f1b3a1c0619ECB80e3a676a6cFabe72Adc

DaoRegister

0x153C3eF051C7665c9A9c1a718bF12bC8EE6b5115

-

0x7229Dc0117E8484D61137B7304b02b163beC912c

The "Client" uses to encrypt the message.

The identity is also used to login to to encrypt or decrypt messages in the "Client".

Relation Name Service contract conforms to the standard published by Relation Protocol.

"Client" is a front-end application. It can be a Web application, Chrome plugin, desktop or mobile application. In Relation ONE, it is a .

[]

[]

Users manage contacts using conforming to Relation Protocol.

Group chats are managed using . The addresses listed in the contract which hold Semantic SBTs are considered group members.

"Client" manages groups by calling .

A Client(A)encrypts payload of the message using Lit, with as the receiver's Ethereum address. The receiver should also be the receiver's Ethereum address. Then, the procedure composites "Messages" according to the [Message Structure](#7.1. Message structure) and sends it to Proxy. The Proxy will push the messages to the Client (B) logged in with the receiver's address.

When sending group messages, a user A logs in a Client(A)and encrypts the message's payload with Lit, with the set to be visible to group members.) The receiver should be set to the group chat's contract address. Then, the procedure composites "Messages" according to the [Message Structure](#7.1. Message structure) and sends it to Proxy. The Proxy will then push the messages to each Client of the users in the group, such as Client(C)、Client(D)、Client(E)...

[Client](#3. Client) implements . Each message is encrypted with the Client on the sender's side and decrypted with the Client on the receiver's side.

Lit Protocol
Lit Protocol
Name Service
Chrome plugin
Follow contract
DAO contracts
standard DAO interface
accessCondition
accessCondition
Lit Protocol SDK
name-service docs
follow docs
DAO docs
Message
Message
Chat
Relation Protocol
Relation Name Service
Relationship
Lit Protocol
Arweave
Fig. 1-1 IM Architecture
Fig. 1-2 Relation Name Service
Fig. 1-3 Send Message
Fig. 1-4 Follow
Fig. 1-5 DAO
Fig. 1-6 Encrypt and Decrypt messages
Fig. 1-7 Message storage