@windingtree/wt-js-libs
Version:
Javascript libraries to interact with the Winding Tree contracts
182 lines (168 loc) • 6.09 kB
JavaScript
import Web3Eth from 'web3-eth';
import Web3Utils from 'web3-utils';
import {
TrustClueConfigurationError,
TrustClueRuntimeError,
} from './errors';
/**
* TrustClueClient is a static factory class that is responsible
* for creating proper instances of `TrustClueInterface`s.
* It is configured during the library initialization.
*
* Please bear in mind, that once the clues are configured, the
* configuration is shared during the whole runtime.
*/
export class TrustClueClient {
/**
* Initializes the map of `TrustClue`s.
*
* @param {TrustClueClientOptionsType}
* @throws {TrustClueConfigurationError} when there are multiple clues with the same name
*/
static createInstance (options) {
options = options || {};
const clues = {};
// Convert all trust clue names to lowercase.
for (const key of Object.keys(options.clues || {})) {
const normalizedKey = key.toLowerCase();
if (clues[normalizedKey]) {
throw new TrustClueConfigurationError(`Clue declared twice: ${normalizedKey}`);
}
clues[normalizedKey] = options.clues[key];
}
options.clues = clues;
return new TrustClueClient(options);
}
constructor (options) {
this._clues = {};
this.options = options || {};
this.clueNameList = Object.keys(options.clues);
this.web3Eth = new Web3Eth(options.provider);
}
/**
* Returns a fresh instance of an appropriate TrustClue by
* calling the `create` function from the clue's configuration.
*
* @throws {TrustClueRuntimeError} when name is not defined or a clue with such name does not exist
*/
async getClue (name) {
name = name && name.toLowerCase();
if (this._clues[name]) {
return this._clues[name];
}
if (!name || !this.options.clues[name]) {
throw new TrustClueRuntimeError(`Unsupported trust clue type: ${name || 'null'}`);
}
const clueDeclaration = this.options.clues[name];
const clueInstance = await clueDeclaration.create(clueDeclaration.options);
this._clues[name] = clueInstance;
return this._clues[name];
}
/**
* Returns a list of metadata for all clues.
*/
async getMetadataForAllClues () {
const trustClues = [];
for (let i = 0; i < this.clueNameList.length; i++) {
const clue = await this.getClue(this.clueNameList[i]);
trustClues.push(await clue.getMetadata());
}
return trustClues;
}
/**
* Walks over all clues and collects their values.
* @param {string} address Ethereum address for which the values should
* be collected.
* @returns A list of objects with either value or error:
* `{name: clue-name, value: value, error: error message}`
*/
async getAllValues (address) {
const promises = [];
for (let i = 0; i < this.clueNameList.length; i++) {
const getClueValue = this.getClue(this.clueNameList[i])
.then((clue) => {
return clue.getValueFor(address);
})
.then((value) => ({
name: this.clueNameList[i],
value: value,
}))
.catch((e) => ({
name: this.clueNameList[i],
error: e.toString(),
}));
promises.push(getClueValue);
}
return Promise.all(promises);
}
/**
* Walks over all clues and collects their interpreted values.
* @param {string} address Ethereum address for which the values should
* be collected.
* @returns A list of objects with either value or error:
* `{name: clue-name, value: value, error: error message}`
*/
async interpretAllValues (address) {
const promises = [];
for (let i = 0; i < this.clueNameList.length; i++) {
const getClueValue = this.getClue(this.clueNameList[i])
.then((clue) => {
return clue.interpretValueFor(address);
})
.then((value) => ({
name: this.clueNameList[i],
value: value,
}))
.catch((e) => ({
name: this.clueNameList[i],
error: e.toString(),
}));
promises.push(getClueValue);
}
return Promise.all(promises);
}
/**
* Verifies the signature and actual signer.
*
* @param {string} serializedData String data to be signed.
* @param {string} signature Strictly hex encoded (starting with 0x) signature of `serializedData`.
* @param {function} verificationFn Optional verification function. Is called with the actual
* signer and should throw when verification fails. The return value is ignored.
* Default: Parse `serializedData` as JSON and compare the actual signer to checksum
* address of the `signer` field.
*
* Default verification fn expects the `serializedData` is string containing JSON with `signer` field
* Provide custom `verificationFn` if needed.
*
* @throws {TrustClueRuntimeError} When any of arguments is missing, or the signature recovery
* fails or the signature verification fails or any other error occurs.
*/
async verifySignedData (serializedData, signature, verificationFn) {
if (!serializedData) {
throw new TrustClueRuntimeError('serializedData is missing.');
}
if (!signature || !Web3Utils.isHexStrict(signature)) {
throw new TrustClueRuntimeError('signature is either missing or not hex encoded with 0x prefix.');
}
if (!verificationFn) {
verificationFn = async (_actualSigner) => {
const data = JSON.parse(serializedData);
const expectedSigner = data.signer;
if (Web3Utils.toChecksumAddress(expectedSigner) !== _actualSigner) {
throw new TrustClueRuntimeError(`Expected signer '${expectedSigner}' does not match the recovered one '${_actualSigner}'`);
}
};
}
try {
const actualSigner = this.web3Eth.accounts.recover(serializedData, signature);
try {
await verificationFn(actualSigner);
} catch (e) {
throw new TrustClueRuntimeError(`Verification function failed: ${e.toString()}`, e);
}
} catch (e) {
throw new TrustClueRuntimeError(e);
}
}
}
export default TrustClueClient;