User Storage

Describes how GoodDAPP uses Gun p2p database, to store user owned data.



We chose Gun since it's a javascript based, decentralized (p2p) with built in encryption database. Check out their website for more info


You can import the UserStorage instance anywhere in the code by doing

import userStorage from '/lib/gundb/UserStorage'
//you can make sure its initialized by waiting on 'ready'
await userStorage.ready


The constructor calls init(), which logs in the user into Gun. The user's password+pass are generated deterministically by signing a message with one of his HD wallet private keys.



The profile holds information of the user (signed with Gun SEA, so only he can modify it) such as:

  • Name

  • Email

  • Mobile

  • Wallet Address

  • Username


Each profile field is an object of type:

export type ProfileField = {
  value: EncryptedField,
  display: string,
  privacy: FieldPrivacy
  • value - is the SEA encrypted value, so only the user can read it.

  • display - is the string displayed on the DAPP to other users

  • privacy - is the privacy level of the field (masked, public, private)

Fields such email and mobile can be set to be public, private or masked, this is in order to let the user control what information he wishes to disclose. If they are public then users will be able to send funds to the user directly by simply typing his mobile, email or username in the app.


We keep the users' profiles indexed by email, mobile (in case they are public), wallet address and username. This enables to connect blockchain transactions to user profiles. Specifically it is used in the user feed and in the "Send" flow to enable directly sending GoodDollars by mobile, email and username.
   * Generates index by field if privacy is public, or empty index if it's not public
   * @param {string} field - Profile attribute
   * @param {string} value - Profile attribute value
   * @param {string} privacy - (private | public | masked)
   * @returns Gun result promise after index is generated
   * @todo This is world writable so theoritically a malicious user could delete the indexes
   * need to develop for gundb immutable keys to non first user
  async indexProfileField(field: string, value: string, privacy: FieldPrivacy): Promise<ACK> {
    if (!UserStorage.indexableFields[field]) return Promise.resolve({ err: 'Not indexable field', ok: 0 })
    const cleanValue = UserStorage.cleanFieldForIndex(field, value)
    if (!cleanValue) return Promise.resolve({ err: 'Indexable field cannot be null or empty', ok: 0 })

    const indexNode = gun.get(`users/by${field}`).get(cleanValue)
    logger.debug('indexProfileField', { field, cleanValue, value, privacy, indexNode })

An issue with the index is that currently any user can overwrite any entry in the index, since nodes in GunDB are writable by everyone. We are working on an extension to GunDB to create append only nodes so an index key, once set by a user can not be changed by anyone else besides him


let value:string = await userStorage.getProfileFieldValue('email')
//the gun way
let field:ProfileField = await userStorage.profile.get('email').then()


The feed holds all the blockchain transactions the user did but also other system and messaging events.

Feed Indexes

We keep 3 indexes for easy access and display purposes:


Fast access to event details

Each events is encrypted

gun.user().get('feed'). get('byid').get(<eventId>)


By Date(daily)

Display sorted by time to user with a reasonable paging scheme

gun.user().get('feed') .get(<date granularity day>)


Events count by date

Helper for pager to fetch next X events

gun.user().get('feed') .get('index').get(<date granularity day>)


The in memory index is updated on every change to 'index' by add a gundb listener in the method initFeed:

Good first Issues

Last updated