UNPKG

@foreverrbum/ethsign

Version:

This package will allow you to electronically sign documents within your application

693 lines (631 loc) 21.1 kB
import { getCurrentBlockNumber, getIpfsHashFromBytes32, safeToAscii, timer } from "./dashboard"; import { isFileWithPassword } from './download'; import moment from 'moment'; import { getChainData, getChainDataWithoutSigNum, getChainDataWithSigNum, merAddressData, mergeDocumentsData, mergeSignatureData, getSingleChainDataWithouNum, mergeSingleChainDocumentData, mergeSingleChainSignatureData } from "../components/dataVis/utils"; import { getTimestampOfFutureBlock } from "./block"; export const ENDPOINT_PREFIX = 'https://api.thegraph.com/subgraphs/name/ethsign/ethsign-3-'; export const API_VERSION = ''; export const USER_JOINED_QUERY = "User joined query"; export const SIGNATURE_SIGNED_QUERY = 'Signature signed query'; const getNetworkEndpoint = (chainId) => { let endpoint = ENDPOINT_PREFIX; switch(chainId) { case 1: endpoint += 'ethereum'; //'mainnet'; break; case 3: endpoint += 'ropsten'; break; case 56: endpoint += 'bsc'; break; case 43114: endpoint += 'avalanche'; break; case 137: endpoint += 'polygon'; break; case 1287: endpoint += 'moonbeam'; break; case 250: endpoint += 'fantom'; break; default: throw new Error("Unsupported Network for GraphQL Query with Chain ID " + chainId); } endpoint += API_VERSION; return endpoint; } export const getDeploymentId = (chainId) => { switch(chainId) { case 1: return 'QmaoPWdPLK815q2ygFbWTE339HRQfpvPVzAbg5Yjj72o4B'; case 3: return 'QmUu2yAmfD6EwUP27PFwNkzjXGVnfxZkXkf21XfgN1X3Sm'; case 56: return 'QmQxmQcWvpsw8V3BPEqGAQe3NRqvVaJREpyAYzBw7gA3mk'; case 43114: return 'QmapYZcJu7GWNUg3Uf2vck37PRDPPmiZv3qZDQzs5x35v6'; case 137: return 'QmecH6azJ2x5aSns8NRJVuQyGdgw9F94SiDK3wm3zw6Wi2'; case 250: return 'QmdLz9Wd1dUaFVTT7tFQ8JwMPzLK1RjBuk4iohVqCcwUzZ'; default: throw new Error("Unsupported Network for GraphQL Query with Chain ID " + chainId); } } export const getContractHistoryQuery = (contractId) => { return ` { events(where: {contract: "${contractId}"}, orderBy: blockNumber, orderDirection: asc, first: 1000) { blockNumber timestamp type provider storage_id0 storage_id1 involvedParty number } } `; } export const getBlockIndexStatusQuery = (chainId) => { return ` { indexingStatuses(subgraphs: ["${getDeploymentId(chainId)}"]) { chains { chainHeadBlock { number } earliestBlock { number } latestBlock { number } } } } `; } export const getSignatureValidityQuery = (docKey, signerAddress) => { return ` { contracts(where: {id: "${docKey}"}) { name currentSignedSignersList(where: {id: "${signerAddress}"}) { id } } events(where: {contract: "${docKey}", type: LogSignedDocument, involvedParty: "${signerAddress}"}) { timestamp } } `; } export const getSignatureExistenceQuery = (docKey, signerAddress) => { return ` { contracts(where: {id: "${docKey}"}) { name currentSignersList(where: {id: "${signerAddress}"}) { id } } } `; } export const getContractsGivenAddressQuery = (address) => { return ` { users(where: {id: "${address}"}) { contracts(orderBy: birth, orderDirection: desc) { type signed contract { id status } } } } `; } export const getContractDetailsGivenDocKeyQuery = (documentKey) => { return ` { contracts(where: { id: "${documentKey}" }) { initiator { id } name birth expiration docStorage { provider storage_id0 storage_id1 } metaStorage { provider storage_id0 storage_id1 } totalSigners currentSigners currentSignedSigners currentSignedSignersList { id } status events { type } commentStorageList { commentAuthor provider storage_id0 storage_id1 } } } `; } export const getAllContractDataHistoryData = () => { return ` { infos(first: 1) { totalDocumentsSigned totalSignaturesSigned } } ` } export const getDocumentSignedQuery = (first = 100 , skip = 0) => { return ` { contracts(where: {consensusTimestamp_lt: ${moment().format('X')}}, first: ${first}, skip: ${skip}) { name id consensusTimestamp } } ` } export const getSignatureSignedQuery = (first = 100 , skip = 0) => { return ` { events(where: {timestamp_lt: ${moment().format('x')}, type: LogSignedDocument}, first: ${first}, skip: ${skip}) { type timestamp involvedParty } } ` } export const getAddressQuery = (first = 100 , skip = 0) => { return ` { users(where: {joinedTimestamp_lt: ${moment().format('X')}}, first: ${first}, skip: ${skip}) { id joinedTimestamp } } ` } export const getContractSignatureData = (documentKey) => { return ` { contracts(where: {id: "${documentKey}"}) { initiator { id } id name birth signatureInfo { signer { id } signatureFieldsSigned } } } ` } export const loadData = async (chainId, documentKey) => { let endpoint = getNetworkEndpoint(chainId); // Check to see if it's a valid chainId if(endpoint.length == 0) { return []; } let res; await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ query: getContractHistoryQuery(documentKey), }), }).then((res) => res.json()).then((result) => { if(!result.data.events) { res = {}; return; } const newData = []; let initiator = null; result?.data.events.forEach((item) => { const {type, timestamp, involvedParty, blockNumber, provider, storage_id0, storage_id1, number} = item; switch (type){ case "LogNewDocument": initiator = involvedParty; newData.push({ type, timestamp, blockNumber, initiator: involvedParty, actionString: 0 }); break; case "LogChangedDocumentStorage": newData.push({ type, timestamp, blockNumber, provider, storage_id0, storage_id1, actionString: 1 }); break; case "LogAddedNewSignerForDocument": newData.push({ type, timestamp, blockNumber, initiator: involvedParty, actionString: 2 }); break; case "LogSetNumberOfSignatureFields": newData.push({ type, timestamp, blockNumber, initiator: initiator, signer: involvedParty, number, actionString: 3 }); break; case "LogSignedDocument": newData.push({ type, timestamp, blockNumber, initiator: involvedParty, actionString: 4 }) break; } }) res = newData; }); return res; } export const loadContractDetails = async (chainId, documentKey, provider, address, signal) => { let endpoint = getNetworkEndpoint(chainId); // Check to see if it's a valid chainId if(endpoint.length == 0) { return []; } let res = {}; await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', }, signal:signal, body: JSON.stringify({ query: getContractDetailsGivenDocKeyQuery(documentKey), }), }).then((res) => res.json()).then(async (result) => { if(!(result?.data?.contracts?.length > 0)) { res = null; return; } let created_at = null; if(provider) created_at = await provider.getBlock(parseInt(result?.data.contracts[0].birth)); let expire_at = null, expiration_block = parseInt(result?.data.contracts[0].expiration); if (parseInt(result?.data.contracts[0].expiration) !== 0) { let estimateDate = null; if(provider) estimateDate = await getTimestampOfFutureBlock(parseInt(result?.data.contracts[0].expiration), await getCurrentBlockNumber(provider), provider) expire_at = estimateDate ? moment(estimateDate).unix() : null } else { expiration_block = 'Never' } /* * Statuses: * 0: 'PDF Not Uploaded' * 1: 'More Signers Needed' * 2: 'Pending Signatures' * 3: 'All Signed' * 4: 'Waiting For Others' */ let status = parseInt(result?.data.contracts[0].status); if(status == 2) { let userHasSigned = false; for(let signer of result?.data.contracts[0].currentSignedSignersList) { if(signer.id === address?.toLowerCase()) { userHasSigned = true; } } if(userHasSigned) { status = 4; } } const docStorageProvider = result?.data.contracts[0].docStorage.provider const docStorage_id0 = result?.data.contracts[0].docStorage.storage_id0 const docStorage_id1 = result?.data.contracts[0].docStorage.storage_id1 // const metaStorageProvider = result?.data.contracts[0].metaStorage.provider const metaStorage_id0 = result?.data.contracts[0].metaStorage.storage_id0 const metaStorage_id1 = result?.data.contracts[0].metaStorage.storage_id1 let docHash = getIpfsHashFromBytes32(docStorage_id0) + getIpfsHashFromBytes32(docStorage_id1); let metaHash = getIpfsHashFromBytes32(metaStorage_id0) + getIpfsHashFromBytes32(metaStorage_id1); let withPassword = await isFileWithPassword(docHash); let commentData = []; let commentStorageList = result?.data.contracts[0].commentStorageList; if(commentStorageList) { commentStorageList.map((data) => { let commentHash = getIpfsHashFromBytes32(data.storage_id0) + getIpfsHashFromBytes32(data.storage_id1); if(commentHash != '') { commentData.push({ address: data.commentAuthor.toLowerCase(), provider: safeToAscii(data.provider), commentHash: commentHash }); } }); } // with the assumption that all files (doc, meta, comments) are stored at the same storageprovider const storageProvider = safeToAscii(docStorageProvider); let {signatureData, signers} = await loadContractSignatureDetails(chainId, documentKey, signal); const {totalSigners, currentSigners} = result?.data.contracts[0] if(totalSigners != currentSigners){ status = 1; } // initiator = _.signers.filter() res.documentKey = documentKey; res.initiator = result?.data.contracts[0].initiator.id.toLowerCase(); res.name = safeToAscii(result?.data.contracts[0].name).replace(/\0.*$/g, ''); res.creation = { block: parseInt(result?.data.contracts[0].birth), date: created_at.timestamp }; res.expiration = { block: expiration_block, date: expire_at ? expire_at : null }; res.status = status; res.ipfsHash = docHash; res.metaHash = metaHash; res.storageProvider = storageProvider; res.commentData = commentData; res.withPassword = withPassword; res.signers = signers; res.signatureData = signatureData; }).catch((err) => { console.log(err); }); return res; } export const loadContractSignatureDetails = async (chainId, documentKey, signal) => { let endpoint = getNetworkEndpoint(chainId); // Check to see if it's a valid chainId if(endpoint.length == 0) { return {signatureData: [], signers: []}; } let signatureData = []; let signers = []; await fetch(endpoint, { method: 'POST', signal: signal, headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ query: getContractSignatureData(documentKey), }), }).then((res) => res.json()).then(async (result) => { if(result?.data.contracts[0]?.signatureInfo?.length > 0) { result?.data.contracts[0].signatureInfo.map((sigInfo) => { const signed = sigInfo.signatureFieldsSigned.filter(field => field == true); const numSignedFields = signed.length; const numTotalFields = sigInfo.signatureFieldsSigned.length; signatureData.push({ signer: sigInfo.signer.id.toLowerCase(), signed: signed.length, notSigned: numTotalFields-numSignedFields, fieldSigned: sigInfo.signatureFieldsSigned }) signers.push({ address: sigInfo.signer.id.toLowerCase(), avatar: null, alias: null, fullySigned: numSignedFields == numTotalFields }) }) } }).catch((err) => { console.log(err); }); return {signatureData: signatureData, signers: signers}; } export const loadContracts = async (chainId, address, signal) => { let endpoint = getNetworkEndpoint(chainId); // Check to see if it's a valid chainId if(endpoint.length == 0) { return []; } let data = []; await fetch(endpoint, { method: 'POST', signal: signal, headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ query: getContractsGivenAddressQuery(address.toLowerCase()), }), }).then((res) => res.json()).then(async (result) => { if(result?.data?.users[0]) { data = result.data.users[0].contracts; for(let c of data) { c.ethAccount = address.toLowerCase(); } // mine_data = result.data.users[0].createdByMe; // archived_data = result.data.users[0].archived; // shared_data = result.data.users[0].sharedWithMe; } }).catch((err) => { console.log(err); }); return data; } export const getChainIndexStatus = async (chainId) => { let endpoint = 'https://api.thegraph.com/index-node/graphql'; let data = []; let blocksBehind = 0; await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ query: getBlockIndexStatusQuery(chainId), }), }).then((res) => res.json()).then(async (result) => { if(result?.data?.indexingStatuses[0]) { data = result.data.indexingStatuses[0].chains[0]; blocksBehind = data.chainHeadBlock.number - data.latestBlock.number; } }); return blocksBehind; } export const validateSignature = async (chainId, docKey, signerAddress) => { // STATUS: // 0 - Does not exist // 1 - Signature Verified // 2 - Signature Pending let endpoint = getNetworkEndpoint(chainId); // Check to see if it's a valid chainId if(endpoint.length == 0) { return { status: 0, networkId: chainId, doc: null, signerDetails: null, }; } let ret = null; await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ query: getSignatureValidityQuery(docKey, signerAddress.toLowerCase()), }), }).then((res) => res.json()).then(async (result) => { if(result.data?.contracts?.length > 0 && result.data?.events?.length > 0) { // Valid signature ret = { status: 1, networkId: chainId, doc: { name: safeToAscii(result.data.contracts[0].name).replace(/\0.*$/g, ''), key: docKey, }, signerDetails: { address: signerAddress, timestamp: result.data.events[0].timestamp, } } } else { // Not a valid signature await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ query: getSignatureExistenceQuery(docKey, signerAddress.toLowerCase()), }), }).then((res) => res.json()).then((result) => { if(result.data?.contracts?.length > 0 && result.data?.contracts[0].currentSignersList?.length > 0) { ret = { status: 2, networkId: chainId, doc: { name: safeToAscii(result.data.contracts[0].name).replace(/\0.*$/g, ''), key: docKey, }, signerDetails: { address: signerAddress, timestamp: null, }, } } else { ret = { status: 0, networkId: chainId, doc: null, signerDetails: null, }; } }) } }); return ret; } // the network should be ['mainnet', 'ropsten', 'bsc', 'avalanche', 'polygon', 'moonbeam','fantom'] , but in the 'moonbeam' have some bug, we can not get the right data export const network = ['ethereum', 'ropsten', 'bsc', 'avalanche', 'polygon', 'fantom']; export const loadAllContractHistoryData = async () => { return await getChainData(ENDPOINT_PREFIX, "", getAllContractDataHistoryData()); } export const loadAllDocumentsSignedData = async (documentsSignedNumOnEachChain = []) => { return mergeDocumentsData(await getChainDataWithSigNum(documentsSignedNumOnEachChain, getDocumentSignedQuery, USER_JOINED_QUERY)); } export const loadAllSignatureSignedData = async (signaturesSignedNumOnEachChain) => { return mergeSignatureData(await getChainDataWithSigNum(signaturesSignedNumOnEachChain, getSignatureSignedQuery, USER_JOINED_QUERY)); } export const loadAllAddressCount = async () => { return merAddressData(await getChainDataWithoutSigNum(network, getAddressQuery, USER_JOINED_QUERY)); } export const loadDocumentsSignedByNetwork = async (networkName) => { return mergeSingleChainDocumentData(await getSingleChainDataWithouNum(networkName, getDocumentSignedQuery, USER_JOINED_QUERY)); } export const loadSignatureSignedByNetwork = async (networkName) => { return mergeSingleChainSignatureData(await getSingleChainDataWithouNum(networkName, getSignatureSignedQuery, USER_JOINED_QUERY)); } export const fetchChainData = async (endpoint, networkName, query) => { return await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ query }), }) .then(res=> res.json()) .then(({data}) => { return {networkName, data}; }) }