<

The amalgamation of DIDs and decentralized DBs for portfolio management

portex Feb 23, 2021

Greetings everyone 👋. We wanted to share our thrilling experience of building an exciting application in a short period of time at the ETHDenver hackathon this year. The problem we tried to solve at the hackathon was something that our team was discussing and ideating for some time. But we never took a real initiation until ETHDenver. We thought that it was the right podium as the hackathon was virtual and participating in any such events is great to update ourselves with the latest tools and explore what’s trending.

What did we build?

As mentioned earlier, the problem that we wanted to solve was something that we have been discussing for some time and we felt that everyone could relate to - “How to create non-custodial portfolio management and sharing in a secure manner?”. We have been using portfolio management tools such as CoinStats, DeBank, Zerion for some time and they are really handy. But when it comes to linking our crypto portfolios to our actual identity and exchanging them with certain people or institutions in a confidential yet trustless manner they are inferior tools.

So, we wanted to create a tool where user can secretly manage their portfolio details yet tied to their identity and exchange such information on a need-to-know basis. Also, making sure that the entire mechanism takes place in a trustless fashion. Portex was our initial effort to make this happen.

Building it with decentralized storage protocols was our obvious choice. We felt that there is no better way to interact with the IPFS storage network than Textile tools and APIs. We also had some specific requirements that Textile tools provided out of the box. Along with this, we had a requirement for decentralized identity and immutable user documents that were fulfilled by Ceramic IDX libraries and Ceramic network itself. Honestly, we felt that working and interoperability of Ceramic libraries and Textile libraries was seamless.

How did we build?

As we mentioned already, we wanted to build a tool where users can add/update crypto portfolio information. This information is attached to user identity and kept secret and shared with other users when needed. We achieved the secret portfolio construction with the help of Ceramic IDX APIs and DID providers JWE implementation. The encrypted data are maintained on the user document and the secret to decrypt the document is exchanged upon approval. Although we wanted to include most of the user portfolio information on the user-owned Ceramic document, we knew that we needed a mechanism to exchange the requests and information for the counterparty to decode the portfolio. This is where we went in search of decentralized storage solutions. Turns out Textile ThreadDB is what we were looking for as it acts like an open database that user can read and write to without relying on some service to hold and manage the data.

User directly communicates with ThreadDB and IDX as they are interoperable

Textile ThreadDB to the rescue:

We had envisioned that ThreadDB had a lot to offer to the Portex implementation, but it turned out to be the crucial missing component of the application. ThreadDB helped us with mainly two aspects of the project.

  • To maintain and list all the user details and their identities so that everyone can identify each other on the platform.
  • To request and exchange the portfolio information.

Maintaining registered users:

When a user  logged into the application for the first time, along with creating a profile on IDX we create an entry on the ThreadDB as well. We used the following simplistic schema:

const registrationSchema = {
        $schema: 'http://json-schema.org/draft-07/schema#',
        title: 'RegisterUser',
        type: 'object',
        properties: {
            _id: {type:'string'},
            did: {type:'string'},
            name: {type: 'string'},
            email: {type: 'string'},
            sharedWith: {type:'array'},     // contains user with whom data is shared
            requests: {type:'array'},       // contains all incoming requests
            sharedData: {type:'array'},     // contains all shared portfolios
            requested: {type:'array'},      // contains all outgoing requests
            aesKey: {type:'object'}
        },
    }

Schemas are similar to the schema on conventional databases. You can read more about it here.

Each entry for the user stores some basic information along with a unique identity (DID) assigned by the Ceramic provider. The `aesKey` is the encrypted distinct symmetric key that will be later confidentially shared with other users while exchanging the portfolio. The rest of the fields are initially empty and updated while requesting and sharing the portfolios.

Exchanging portfolio details:

Once the users are onboarded to Portex, they can update their own portfolios and request each other’s portfolios.  ThreadDB doesn’t directly store any portfolio document but the information while exchanging the portfolio is maintained on each user entry.

Two operations that happen during an exchange are a portfolio request and portfolio share. When a user performs a request, the request is added to the recipient user’s entry (`requests`) as well as their own user entry (`requested`) for record-keeping. The following function performs this operation:

const requestPortfolio = async function(sender, receiver){
    const {threadDb, client} = await getCredentials()
    const threadId = ThreadID.fromBytes(threadDb)
    let query = new Where('did').eq(receiver.did)
    let user = await client.find(threadId, 'RegisterUser', query)
    if (user[0].requests.length===0) {
        user[0].requests = [{
            senderDid:sender.did,
            name: sender.name
        }]
    }else {
        user[0].requests.push({
            senderDid:sender.did,
            name: sender.name
        })
    }
    await client.save(threadId,'RegisterUser',[user[0]])

    query = new Where('did').eq(sender.did)
    user = await client.find(threadId, 'RegisterUser', query)
    if (user[0].requested.length===0) {
        user[0].requested = [{
            receiverDid:receiver.did,
            name: receiver.name
        }]
    }else {
        user[0].requested.push({
            receiverDid:receiver.did,
            name: receiver.name
        })
    }
    await client.save(threadId,'RegisterUser',[user[0]])
    return true
}

The counterparty user for whom the request has been made can accept or reject them. If accepted, a confidential key that can only be only unlocked by the counterparty along with a few other information is added to the user’s entry. The below function code snippet shows how the confidential portfolio information is added to the user’s `sharedData` entry.

const sharePortfolio = async function(sender, receiver, documentId, encKey){
    try{

        const {threadDb, client} = await getCredentials()
        const threadId = ThreadID.fromBytes(threadDb)

        // update sender sharedWith array
        let query = new Where('did').eq(sender.did)
        let user = await client.find(threadId, 'RegisterUser', query)
        if(user[0].docID === '0'){
            user[0].docID = documentId
        }
        if (user[0].sharedWith.length===0){
            user[0].sharedWith = [receiver.senderDid]
        }else {
            user[0].sharedWith.push(receiver.senderDid)
        }
        await client.save(threadId,'RegisterUser',[user[0]])

        // update receiver sharedData array
        query = new Where('did').eq(receiver.senderDid)
        user = await client.find(threadId, 'RegisterUser', query)
        if (user[0].sharedData.length===0){
            user[0].sharedData = [{
                encryptedKey: encKey,
                documentId: documentId,
                senderName: sender.name,
                senderEmail: sender.email,
                senderDid: sender.did
            }]
        }else {
            user[0].sharedData.push({
                encryptedKey: encKey,
                documentId: documentId,
                senderName: sender.name,
                senderEmail: sender.email,
                senderDid: sender.did
            })
        }
        await client.save(threadId,'RegisterUser',[user[0]])
        return true
    }catch (e) {
        console.log("Err:",e)
        return false
    }

}

Along with these core functions, we have written several methods to query the threadDB whenever data was needed. All the interactions can be found in this file.

This is how the core mechanisms of the portfolio exchange are performed with the help of a decentralized DB.

We have also made sure that any client interacting with the DB is authenticated with the help of middleware.

The verdict of amalgamation of Ceramic IDX and Textile ThreadDB:

After working with Textile ThreadDB and Ceramic IDX for some time, we believe that we are qualified to give our opinion on such integration. We feel that both decentralized identity and storage space has a long way to go and we are just getting started. Although Ceramic IDX has just entered the beta stage, we were able to build all the key components that Portex demanded at this stage. Integrating Textile ThreadDB with such a solution was seamless and it complemented all the things that IDX had to offer. In our case, it highlighted these benefits:

  • Similar user authentication strategy as Ceramic identity wallet
  • Interoperable user identity across ThreadDB and IDX
  • Inexpensive writes when compared to writing it to the blockchain itself
  • Faster reads when compared to reading transactions from the blockchain

Overall it was a successful amalgamation of these two technologies and we couldn’t think of anything else that we could have used to build our first version of Portex. With more features to build, the integration will only get better.

The road ahead for Portex and ThreadDB:

Portex was an idea that we brought into the conversation whenever we discussed decentralized finance (DeFi). We were passionate about the idea since we believed that user identity and privacy is going to be crucial when DeFi gets mainstream adoption. When we started building this during the hackathon, we had no strong intentions to build further on it. But the response we received during the hackathon made us rethink. So, we have decided to refactor and build a few features that we think will excite the DeFi ecosystem 🚀.

Textile ThreadDB was a crucial component of the application and it will continue to be part of our further development. Folks at Textile are creating various tools to easily onboard developers and users to witness the benefits of decentralization. As we build on Portex we will explore further integrations with Textile tools along with ThreadDB.

That’s it for this post on our experience building with cutting-edge decentralized storage tools. You can check out our ETHDenver submission here where we bagged Textile and Ceramic bounties and also recognized as one of the top 20 hacks 🎉. Keep an eye on the repo and social media handle if you want to contribute or excited to use Portex. We promise to update you all soon with our roadmap , Cheers!



Tags

Koshik Raj

Jazzing @ Consenso Labs

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.