E-Sign documents with blockchain — A Decentralized Approach to Manage Documents
The Web3 way of managing documents

The robust and trustless solution to sign, manage, store, and modify documents.
Everything is powered by the next-generation layer-one blockchain NEAR platform.
Blockchain Choice Justification
NEAR is not the most popular blockchain at the time of writing but it has a very good foundation to be chosen for a decentralized application that is at least theoretically a mass consumer-focused app.
It needs to be cheap and fast, it should be scalable and allow users to easily enter it without having any prior setup and it also should not impact the environment. Compared to Ethereum, NEAR has it and make it beneficial for such use case.
The Problem Description
We all sign or at least acknowledge documents or agreements with various parties. These can be banks, real estate agents, and landlords. The same is true for companies that do endeavors formalized by means of agreements with consumers and other businesses
A single agreement that bounds two parties can have several revisions issued and thus management (i.e storing, signing, issuing) of this all is complex.
There are more and more such solutions to reduce the friction of document management but at the time of writing, none of these is based on web3. That is why NEAR Document Cloud is yet another solution to this problem but this time there is decentralization involved.
User Interface
The UI layer is currently at the wireframing stage. Generally, my personal opinion is that any UI is infinitely better than no UI even for means of communication among technical guys. It automatically makes everyone on the same page and clearly understands the idea.
Let’s discuss the NEAR document cloud’s most important frames and treat it as the best explanation of the whole project’s purpose
Listing agreements
Here we show all agreements either issued by or issued for the logged-in user. This is a kind of a welcome page where the user can immediately see any actions to be performed i.e sign a newly issued agreement before some due date.

On the agreements list we also can see a clear distinction between agreements issued by me and those issued for me (where I am supposed to sign)
Issuing new agreement

Any user can add an agreement. Adding agreement requires input at least ipfs link to the initial document version and intended signer. All remaining details are a matter of potential extension and decision on where to store these (on-chain vs off-chain).
Signing agreements
Users can sign the agreement only if it has been issued with explicit indication he/she is the intended signer. Trying to sign an agreement that has not been issued for acting user will throw (reject tx).

In the image above we can see the Agreement with the credit cart operator not signed
because it has been updated - the issuer has added revision two
and to formally be in force this agreement needs to be signed again. This can be easily achieved by clicking the sign with the wallet button and acknowledging the tx.
Adding new revision
User can only add revision for those agreements he/she has issued. Trying to add a revision to an agreement issued by someone else will result in transaction rejection.

Adding new revisions could potentially be achieved on a separate screen and require just entering ipfs link
to a new document. All other details of the agreement like previous links, signing history, or other details should be read-only at this stage.
Adding a new revision sets the whole agreement in isSigned : false
the state, no matter if it has been already signed or not. Conditions have changed and the agreement can not be automatically in force/signed without the signer explicitly signing it again.
See it in action
The Code
Agreement contract
Agreement class should have:
issuer
for storing the issuer wallet addressisSigned
for tracking the agreement’s latest revision signed stateversions
for keeping track of all versions of the agreement
Additionally, it has two methods one when the agreement is signed and one when a new revision is added.
Let’s break it down and discuss the code in chunks.
constructor(public uri: string, public signer: string) {
this.issuer = Context.sender;
this.isSigned = false;
this.versions = new PersistentVector<string>(`${uri}-v`);
this.versions.push(uri);
this.uri = uri;
}
In the constructor, we see that each agreement has its signer set to method callee, it is initially not signed, it has added the first version to its versions collection (by means of PersistenVector
), and its uri
is initial URI, event if the current URI will change later on.
@mutateState()
addAgreementVersion(version: string): void { assert(Context.sender == this.issuer, "Only issuer can update
agreement");
this.versions.push(version);
this.isSigned = false;
}
addAgreementVersion
just ensures that the callee is the initial issuer and resets isSigned
flag to false (as after adding a new revision the whole agreement should be signed again since apparently, the conditions have changed) and adds the new version to theversions
collection
@mutateState()
signAgreement(): void{
assert(Context.sender == this.signer, "You're not allowed to sign this");
this.isSigned = true;
}
signAgreement
ensures the callee is the one indicated as the signer and if so it changes the flag isSigned
to true.
The agreement contract usage
Those functions here simulate a kind of a factory class making sure there is a register with all agreements and call functions of each class to present how things work down at the class level.
Originally published at https://rotynski.dev