16 Commits

Author SHA1 Message Date
DwCay
3173ec9342 Fix style for status markers 2022-08-24 19:41:42 +03:00
DuCay
c8352e6537 Merge branch 'master' into DID-13 2022-08-24 17:21:00 +03:00
DwCay
9fbc6423d1 Refactoring code and fix problems display blockchain data 2022-08-24 17:00:43 +03:00
kandrusyak
e4f691ab78 add processed fields 2022-08-24 13:24:10 +03:00
Kirill Andrusyak
446276fadf Merge pull request #57 from franze6/DID-13
Fixed delete and update data account
2022-08-24 10:08:39 +03:00
DwCay
1b574ac347 Fixed delete and update data account 2022-08-24 06:04:21 +03:00
Kirill Andrusyak
0c7db9cd30 Merge pull request #56 from franze6/DID-13
Did 13
2022-08-23 11:36:53 +03:00
DwCay
fc977cc43a Fix decrypted data for blockchain data 2022-08-23 11:17:34 +03:00
DwCay
0a0151d1dd Added location statuses for account data 2022-08-23 05:50:59 +03:00
kandrusyak
369f0e375c add loader 2022-08-22 17:05:05 +03:00
kandrusyak
79ca03823c Change timeout 2022-08-22 15:53:00 +03:00
kandrusyak
49f3ee17a0 tr fix 2022-08-22 15:46:39 +03:00
kandrusyak
841e82e233 update dockerfile 2022-08-17 16:24:49 +03:00
Kirill Andrusyak
2212ea7433 Create docker-image.yml 2022-08-17 15:46:40 +03:00
kandrusyak
f257066d5a blockchain integration 2022-08-17 14:05:49 +03:00
Kirill Andrusyak
027bf0d405 Merge pull request #55 from franze6/DID-12 2022-08-16 17:17:41 +03:00
17 changed files with 1346 additions and 325 deletions

64
.github/workflows/docker-image.yml vendored Normal file
View File

@@ -0,0 +1,64 @@
name: Build and deploy
on:
push:
branches:
- 'master'
tags:
- '*'
paths-ignore:
- '.github/**'
pull_request:
branches:
- 'master'
tags:
- '*'
paths-ignore:
- '.github/**'
workflow_dispatch:
inputs:
logLevel:
description: 'Log level'
required: true
default: 'warning'
type: choice
options:
- info
- warning
- debug
jobs:
build:
runs-on: self-hosted
steps:
- name: Get branch data
uses: actions/checkout@v2
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push image
uses: docker/build-push-action@v2
with:
push: true
tags: kandrusyak/blockchain-ui:master
deploy:
needs: build
name: Deploy to host
runs-on: self-hosted
steps:
- name: Get branch data
uses: actions/checkout@v2
- name: Deploying changes
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USERNAME }}
password: ${{ secrets.SSH_PASSWORD }}
script: |
docker login -u ${{ secrets.DOCKERHUB_USERNAME }} -p ${{ secrets.DOCKERHUB_TOKEN }}
./update.sh

View File

@@ -1,5 +1,7 @@
FROM node:16-alpine AS builder FROM node:16-alpine AS builder
RUN apk --update add --no-cache curl git python3 alpine-sdk bash autoconf libtool automake
WORKDIR /front WORKDIR /front
COPY . . COPY . .

713
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"name": "mosaic-react", "name": "mosaic-react",
"version": "0.1.0", "version": "0.1.0",
"scripts": { "scripts": {
"dev": "webpack-dev-server", "dev": "webpack-dev-server --https",
"build:dev": "webpack --mode development --progress", "build:dev": "webpack --mode development --progress",
"build:prod": "webpack --mode production --progress" "build:prod": "webpack --mode production --progress"
}, },
@@ -24,6 +24,7 @@
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-flatpickr": "^3.10.7", "react-flatpickr": "^3.10.7",
"react-loader-spinner": "^5.2.0",
"react-router-dom": "^6.2.1", "react-router-dom": "^6.2.1",
"react-transition-group": "^4.4.1", "react-transition-group": "^4.4.1",
"stream-browserify": "^3.0.0", "stream-browserify": "^3.0.0",
@@ -34,18 +35,18 @@
"@babel/core": "^7.18.5", "@babel/core": "^7.18.5",
"@babel/preset-env": "^7.18.2", "@babel/preset-env": "^7.18.2",
"@babel/preset-react": "^7.17.12", "@babel/preset-react": "^7.17.12",
"autoprefixer": "^10.4.5",
"babel-loader": "^8.2.5", "babel-loader": "^8.2.5",
"html-webpack-plugin": "^5.5.0",
"css-loader": "^6.7.1", "css-loader": "^6.7.1",
"file-loader": "^6.2.0",
"html-loader": "^3.1.2", "html-loader": "^3.1.2",
"html-webpack-plugin": "^5.5.0",
"postcss": "^8.4.5", "postcss": "^8.4.5",
"postcss-loader": "^7.0.0",
"sass": "^1.52.3", "sass": "^1.52.3",
"sass-loader": "^13.0.0", "sass-loader": "^13.0.0",
"style-loader": "^3.3.1", "style-loader": "^3.3.1",
"file-loader": "^6.2.0",
"postcss-loader": "^7.0.0",
"tailwindcss": "^3.0.18", "tailwindcss": "^3.0.18",
"autoprefixer": "^10.4.5",
"webpack": "^5.73.0", "webpack": "^5.73.0",
"webpack-cli": "^4.10.0", "webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.9.2" "webpack-dev-server": "^4.9.2"

View File

@@ -19,8 +19,11 @@ import Onboarding4 from "./pages/Onboarding4";
import SharedData from "./pages/SharedData"; import SharedData from "./pages/SharedData";
import ResetPassword from "./pages/ResetPassword"; import ResetPassword from "./pages/ResetPassword";
import SignIn from "./pages/SignIn"; import SignIn from "./pages/SignIn";
import {LoadingOverlay} from './components/LoadingOverlay';
import {store} from './store/store';
import {observer} from 'mobx-react-lite';
function App() { const App = observer(() => {
const location = useLocation(); const location = useLocation();
useEffect(()=> { useEffect(()=> {
@@ -35,6 +38,7 @@ function App() {
return ( return (
<> <>
{ store.loading && <LoadingOverlay /> }
<Routes> <Routes>
<Route exact path="/" element={<Onboarding1 />} /> <Route exact path="/" element={<Onboarding1 />} />
<Route path="/digitalId/profile-id" element={<ProfileId />} /> <Route path="/digitalId/profile-id" element={<ProfileId />} />
@@ -50,6 +54,6 @@ function App() {
</Routes> </Routes>
</> </>
); );
} })
export default App; export default App;

View File

@@ -0,0 +1,15 @@
import ReactDOM from 'react-dom';
import React from 'react';
import {ThreeCircles} from 'react-loader-spinner';
export const LoadingOverlay = () => ReactDOM.createPortal((
<div className='absolute left-0 top-0 h-screen w-screen z-50 loading-overlay'>
<ThreeCircles
height="100"
width="100"
color="#6366f1"
visible={true}
ariaLabel="three-circles-rotating"
/>
</div>
), document.body)

View File

@@ -9,3 +9,10 @@
@apply ring-0; @apply ring-0;
} }
} }
.loading-overlay {
background-color: rgba(0, 0, 0, 0.25);
display: flex;
justify-content: center;
align-items: center;
}

View File

@@ -6,6 +6,10 @@ import App from './App'
globalThis.Buffer = Buffer globalThis.Buffer = Buffer
BigInt.prototype.toJSON = function() {
return this.toString()
}
ReactDOM.render( ReactDOM.render(
<React.StrictMode> <React.StrictMode>
<Router> <Router>

View File

@@ -6,6 +6,7 @@ import Header from '../../partials/Header';
import ProfileTable from '../../partials/profile/ProfileTable'; import ProfileTable from '../../partials/profile/ProfileTable';
import Image from '../../images/transactions-image-04.svg'; import Image from '../../images/transactions-image-04.svg';
import {labelMap} from '../../shared/labelMap'; import {labelMap} from '../../shared/labelMap';
import {statusMap} from "../../shared/statusMap";
const initialPropertyValues = ['First name', 'Second name', 'Birthdate', 'Gender', 'National doctype', 'National doc ID', 'National doc issue date', 'National doc expiry date']; const initialPropertyValues = ['First name', 'Second name', 'Birthdate', 'Gender', 'National doctype', 'National doc ID', 'National doc issue date', 'National doc expiry date'];
@@ -15,7 +16,7 @@ const Profile = observer (() => {
value: '', value: '',
seed: String(Math.floor(Math.random() * 90000000000000000000), 10), seed: String(Math.floor(Math.random() * 90000000000000000000), 10),
}; };
const [sidebarOpen, setSidebarOpen] = useState(false); const [sidebarOpen, setSidebarOpen] = useState(false);
const [buttonPanelOpen, setButtonPanelOpen] = useState(true); const [buttonPanelOpen, setButtonPanelOpen] = useState(true);
const [removePanelOpen, setRemovePanelOpen] = useState(false); const [removePanelOpen, setRemovePanelOpen] = useState(false);
@@ -28,7 +29,8 @@ const Profile = observer (() => {
const [updatedValues, setUpdatedValues] = useState([defaultValues]); const [updatedValues, setUpdatedValues] = useState([defaultValues]);
const [addedValues, setAddedValues] = useState(defaultValues); const [addedValues, setAddedValues] = useState(defaultValues);
const [isCheck, setIsCheck] = useState([]); const [isCheck, setIsCheck] = useState([]);
const [alreadyExists, setAlreadyExists] = useState(false); const [alreadyExists, setAlreadyExists] = useState(false)
const [blockChainValue, setBlockChainValue] = useState('');
useEffect(() => { useEffect(() => {
handleSelectedItems(isCheck); handleSelectedItems(isCheck);
@@ -57,12 +59,15 @@ const Profile = observer (() => {
}; };
const changeUpdatedValues = (value, field, key) => { const changeUpdatedValues = (value, field, key) => {
if(field==='value') {
setBlockChainValue(value)
}
setUpdatedValues((prevState) => setUpdatedValues((prevState) =>
prevState.map((elem) => ( prevState.map((elem) => (
(elem.key === key) ? { (elem.key === key) ? {
...elem, ...elem,
[field]: value, [field]: value,
} : elem } : elem
)) ))
) )
}; };
@@ -127,8 +132,10 @@ const Profile = observer (() => {
}; };
const deleteDataParameters = () => { const deleteDataParameters = () => {
const changeData = store.decryptedAccountData.filter(item=>!selectedItems.includes(item.key)) const changeData = store.decryptedAccountData.filter(item=>!selectedItems.includes(item.key));
store.pushAccountData(changeData); store.pushAccountData(changeData);
if(storeOnBlockchain)
store.pushAccountDataToBlockchain(changeData.filter(elem=>elem.status!=='Stored'))
}; };
const changeInitialArray = () => { const changeInitialArray = () => {
@@ -138,6 +145,7 @@ const Profile = observer (() => {
if (elem.label === item.label) { if (elem.label === item.label) {
newArr.push({ newArr.push({
...elem, ...elem,
status: 'new',
'value': item.value, 'value': item.value,
'seed': item.seed, 'seed': item.seed,
}) })
@@ -153,6 +161,8 @@ const Profile = observer (() => {
const changeDataParameters = () => { const changeDataParameters = () => {
const updatedData = changeInitialArray(); const updatedData = changeInitialArray();
store.pushAccountData(updatedData); store.pushAccountData(updatedData);
if(storeOnBlockchain)
store.pushAccountDataToBlockchain(updatedData.filter(elem=>elem.status!=='Stored'))
}; };
const addDataParameters = () => { const addDataParameters = () => {
@@ -162,6 +172,8 @@ const Profile = observer (() => {
else else
addedValues.key = addedValues.label addedValues.key = addedValues.label
store.pushAccountData(store.decryptedAccountData.concat(addedValues)); store.pushAccountData(store.decryptedAccountData.concat(addedValues));
if(storeOnBlockchain)
store.pushAccountDataToBlockchain(store.decryptedAccountData.concat(addedValues).filter(elem=>elem.status!=='Stored'))
}; };
const cancelAddPanel = () => { const cancelAddPanel = () => {
@@ -352,7 +364,7 @@ const Profile = observer (() => {
required required
onChange={(e) => changeUpdatedValues(e.target.value, 'label', item.key)} onChange={(e) => changeUpdatedValues(e.target.value, 'label', item.key)}
value={item.label} value={item.label}
disabled='disabled' disabled
/> />
</div> </div>
<div> <div>
@@ -364,7 +376,7 @@ const Profile = observer (() => {
type="text" type="text"
required required
onChange={(e) => changeUpdatedValues(e.target.value, 'value', item.key)} onChange={(e) => changeUpdatedValues(e.target.value, 'value', item.key)}
value={item.value} value={item.status===statusMap.blockchained?blockChainValue : item.value}
/> />
</div> </div>
<div> <div>

View File

@@ -43,7 +43,7 @@ function ProfileTable({
image={defaultData.image} image={defaultData.image}
value={data.value.charAt(0).toUpperCase()+data.value.slice(1)} value={data.value.charAt(0).toUpperCase()+data.value.slice(1)}
property={data.label} property={data.label}
status={defaultData.status} status={data.status}
transactions={defaultData.transactions} transactions={defaultData.transactions}
avatars={defaultData.avatars} avatars={defaultData.avatars}
handleClick={handleClick} handleClick={handleClick}
@@ -69,7 +69,7 @@ function ProfileTable({
image={defaultData.image} image={defaultData.image}
value={data.value.charAt(0).toUpperCase()+data.value.slice(1)} value={data.value.charAt(0).toUpperCase()+data.value.slice(1)}
property={data.label} property={data.label}
status={defaultData.status} status={data.status}
transactions={defaultData.transactions} transactions={defaultData.transactions}
avatars={defaultData.avatars} avatars={defaultData.avatars}
handleClick={handleClick} handleClick={handleClick}
@@ -95,7 +95,7 @@ function ProfileTable({
image={defaultData.image} image={defaultData.image}
value={data.value.charAt(0).toUpperCase()+data.value.slice(1)} value={data.value.charAt(0).toUpperCase()+data.value.slice(1)}
property={data.label} property={data.label}
status={defaultData.status} status={data.status}
transactions={defaultData.transactions} transactions={defaultData.transactions}
avatars={defaultData.avatars} avatars={defaultData.avatars}
handleClick={handleClick} handleClick={handleClick}
@@ -121,7 +121,7 @@ function ProfileTable({
image={defaultData.image} image={defaultData.image}
value={data.value} value={data.value}
property={data.label} property={data.label}
status={defaultData.status} status={data.status}
transactions={defaultData.transactions} transactions={defaultData.transactions}
avatars={defaultData.avatars} avatars={defaultData.avatars}
handleClick={handleClick} handleClick={handleClick}

View File

@@ -1,16 +1,23 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { statusMap } from '../../shared/statusMap';
function ProfileTableItem(props) { function ProfileTableItem(props) {
const [descriptionOpen, setDescriptionOpen] = useState(false); const [descriptionOpen, setDescriptionOpen] = useState(false);
return ( return (
<> <>
<tr className={`${props.status !== 'Progress' || 'bg-[#eaf0f6]'}`}> <tr>
<td className="pl-4 pr-4 py-3 whitespace-nowrap w-px"> <td className="pl-4 pr-4 py-3 whitespace-nowrap w-px">
<div className="flex items-center"> <div className="flex items-center">
<label className="inline-flex"> <label className="inline-flex">
<span className="sr-only">Select</span> <span className="sr-only">Select</span>
<input id={props.id} className="form-checkbox" type="checkbox" onChange={props.handleClick} checked={props.isChecked} /> <input
id={props.id}
className="form-checkbox"
type="checkbox"
onChange={props.handleClick}
checked={props.isChecked}
/>
</label> </label>
</div> </div>
</td> </td>
@@ -19,45 +26,51 @@ function ProfileTableItem(props) {
<div className="w-9 h-9 shrink-0 mr-2 sm:mr-4"> <div className="w-9 h-9 shrink-0 mr-2 sm:mr-4">
<img className="rounded-full" src={props.image} width="40" height="40" alt={props.property} /> <img className="rounded-full" src={props.image} width="40" height="40" alt={props.property} />
</div> </div>
<div className="flex flex-col w-60"> <div className="flex flex-col w-60">
<div className="font-semibold text-slate-800 text-base">{props.value}</div> <div className="font-semibold text-slate-800 text-base">
{props.status === statusMap.blockchained || props.status === statusMap.processed
? `${props.value.slice(0, 4)}****${props.value.slice(props.value.length - 4)}`
: props.value}
</div>
<div className="font-normal text-xxs">{props.property}</div> <div className="font-normal text-xxs">{props.property}</div>
</div> </div>
</div> </div>
</td> </td>
<td className="py-3 whitespace-nowrap w-px"> <td className="py-3 whitespace-nowrap w-px px-2.5 text-left min-w-[117px]">
<div className="w-fit text-xs inline-flex font-medium px-2.5 py-1"> <div className="w-fit text-xs inline-flex font-medium py-1">
{(props.status === 'Progress') ? ( {props.status === statusMap.blockchained ? (
<div className="bg-amber-100 text-amber-600 rounded-full text-center px-2.5 py-1"> <div className="bg-amber-100 text-amber-600 rounded-full text-center px-2.5 py-1">{props.status}</div>
{props.status} ) : props.status === statusMap.processed ? (
</div>) : <div className="bg-amber-100 text-amber-600 rounded-full text-center px-2.5 py-1">{props.status}</div>
(props.status === 'Stored') ? ( ) : props.status === statusMap.stored ? (
<div className="bg-slate-700 text-slate-100 rounded-full text-center px-2.5 py-1"> <div className="bg-slate-700 text-slate-100 rounded-full text-center px-2.5 py-1">{props.status}</div>
{props.status} ) : props.status === statusMap.completed ? (
</div>) : <div className="bg-emerald-100 text-emerald-600 rounded-full text-center px-2.5 py-1">{props.status}</div>
(props.status === 'Completed') ? ( ) : (
<div className="bg-emerald-100 text-emerald-600 rounded-full text-center px-2.5 py-1"> <div className="bg-rose-100 text-rose-600 rounded-full text-center px-2.5 py-1">{props.status}</div>
{props.status} )}
</div>) : (
<div className="bg-rose-100 text-rose-600 rounded-full text-center px-2.5 py-1">
{props.status}
</div>)
}
</div> </div>
</td> </td>
<td className="px-2 py-3 whitespace-nowrap w-[340px] box-border"> <td className="px-2 py-3 whitespace-nowrap w-[340px] box-border">
<div className="flex flex-wrap items-center -m-1.5 justify-center"> <div className="flex flex-wrap items-center -m-1.5 justify-center">
<div className="flex -space-x-3 -ml-0.5"> <div className="flex -space-x-3 -ml-0.5">
{(props.avatars) ? ( {props.avatars
props.avatars.map((avatar, index) => ( ? props.avatars.map((avatar, index) => (
<img className="rounded-full border-2 border-slate-100 box-content" key={index} src={avatar} width="32" height="32" alt="Avatar" /> <img
className="rounded-full border-2 border-slate-100 box-content"
key={index}
src={avatar}
width="32"
height="32"
alt="Avatar"
/>
)) ))
): null} : null}
<button className="flex justify-center items-center w-9 h-9 rounded-full bg-white border-2 border-slate-200 hover:border-slate-300 text-indigo-500 shadow-sm transition duration-150"> <button className="flex justify-center items-center w-9 h-9 rounded-full bg-white border-2 border-slate-200 hover:border-slate-300 text-indigo-500 shadow-sm transition duration-150">
<span className="sr-only">Add avatar</span> <span className="sr-only">Add avatar</span>
<svg className="w-4 h-4 fill-current" viewBox="0 0 16 16"> <svg className="w-4 h-4 fill-current" viewBox="0 0 16 16">
<path d="M15 7H9V1c0-.6-.4-1-1-1S7 .4 7 1v6H1c-.6 0-1 .4-1 1s.4 1 1 1h6v6c0 .6.4 1 1 1s1-.4 1-1V9h6c.6 0 1-.4 1-1s-.4-1-1-1z" /> <path d="M15 7H9V1c0-.6-.4-1-1-1S7 .4 7 1v6H1c-.6 0-1 .4-1 1s.4 1 1 1h6v6c0 .6.4 1 1 1s1-.4 1-1V9h6c.6 0 1-.4 1-1s-.4-1-1-1z" />
</svg> </svg>
</button> </button>
</div> </div>
</div> </div>
@@ -74,31 +87,40 @@ function ProfileTableItem(props) {
</div> </div>
</td> </td>
<td className="py-3 whitespace-nowrap w-px pr-1.5"> <td className="py-3 whitespace-nowrap w-px pr-1.5">
<div className="flex items-center"> <div className="flex items-center">
<button <button
className={`text-slate-400 hover:text-slate-500 transform ${descriptionOpen && 'rotate-180'}`} className={`text-slate-400 hover:text-slate-500 transform ${descriptionOpen && 'rotate-180'}`}
aria-expanded={descriptionOpen} aria-expanded={descriptionOpen}
onClick={() => setDescriptionOpen(!descriptionOpen)} onClick={() => setDescriptionOpen(!descriptionOpen)}
aria-controls={`description-${props.id}`} aria-controls={`description-${props.id}`}
> >
<span className="sr-only">Show more</span> <span className="sr-only">Show more</span>
<svg className="w-8 h-8 fill-current" viewBox="0 0 32 32"> <svg className="w-8 h-8 fill-current" viewBox="0 0 32 32">
<path d="M16 20l-5.4-5.4 1.4-1.4 4 4 4-4 1.4 1.4z" /> <path d="M16 20l-5.4-5.4 1.4-1.4 4 4 4-4 1.4 1.4z" />
</svg> </svg>
</button> </button>
</div> </div>
</td> </td>
</tr> </tr>
<tr className={`${!descriptionOpen && 'hidden'} ${props.status !== 'Progress' || 'bg-[#eaf0f6]'}` }> <tr className={`${!descriptionOpen && 'hidden'}`}>
<td colSpan="10" className="px-12 pt-3.5 pb-[14px]"> <td colSpan="10" className="px-12 pt-3.5 pb-[14px]">
<div className="flex items-center gap-x-5"> <div className="flex items-center gap-x-5">
<div> <div>
<label className="block text-sm font-medium mb-1" htmlFor="placeholder">Seed</label> <label className="block text-sm font-medium mb-1" htmlFor="placeholder">
<input id="placeholder" className="form-input w-[396px] bg-white" type="text" placeholder="2342423423423234223" /> Seed
</label>
<input
id="placeholder"
className="form-input w-[396px] bg-white"
type="text"
placeholder="2342423423423234223"
/>
</div> </div>
<div> <div>
<span className="block text-sm font-medium mb-1">Transaction</span> <span className="block text-sm font-medium mb-1">Transaction</span>
<a href="" className="w-[396px] text-slate-400 font-normal text-sm underline">0x12831823791203192418234841238468</a> <a href="" className="w-[396px] text-slate-400 font-normal text-sm underline">
0x12831823791203192418234841238468
</a>
</div> </div>
</div> </div>
{/* Progress validation bar */} {/* Progress validation bar */}
@@ -113,8 +135,11 @@ function ProfileTableItem(props) {
{props.transactions.map((data, index) => ( {props.transactions.map((data, index) => (
<li className="relative py-2" key={index}> <li className="relative py-2" key={index}>
<div className="flex items-center mb-2.5"> <div className="flex items-center mb-2.5">
{(props.transactions[index+1]) ? ( {props.transactions[index + 1] ? (
<div className="absolute left-0 h-full w-0.5 bg-slate-200 self-start ml-2.5 -translate-x-1/2 translate-y-3" aria-hidden="true"></div> <div
className="absolute left-0 h-full w-0.5 bg-slate-200 self-start ml-2.5 -translate-x-1/2 translate-y-3"
aria-hidden="true"
></div>
) : null} ) : null}
<div className="absolute left-0 rounded-full bg-indigo-500" aria-hidden="true"> <div className="absolute left-0 rounded-full bg-indigo-500" aria-hidden="true">
<svg className="w-5 h-5 fill-current text-white" viewBox="0 0 20 20"> <svg className="w-5 h-5 fill-current text-white" viewBox="0 0 20 20">
@@ -123,15 +148,21 @@ function ProfileTableItem(props) {
</div> </div>
<h3 className="pl-9 whitespace-nowrap"> <h3 className="pl-9 whitespace-nowrap">
<span className="text-validateSize font-bold text-slate-800">Validate by </span> <span className="text-validateSize font-bold text-slate-800">Validate by </span>
<a href="" className="font-bold text-validateSize underline text-indigo-500">{data}</a> <a href="" className="font-bold text-validateSize underline text-indigo-500">
{data}
</a>
</h3> </h3>
</div> </div>
<div className="pl-9"> <div className="pl-9">
<a href="" className="w-[396px] text-slate-800 font-semibold text-base underline">{data}</a> <a href="" className="w-[396px] text-slate-800 font-semibold text-base underline">
{data}
</a>
<span className="block text-xxs font-normal mb-1">Transactions ID</span> <span className="block text-xxs font-normal mb-1">Transactions ID</span>
</div> </div>
<div className="pl-9"> <div className="pl-9">
<a href="" className="w-[396px] text-slate-800 font-semibold text-base underline">{data.slice(2, data.length)}</a> <a href="" className="w-[396px] text-slate-800 font-semibold text-base underline">
{data.slice(2, data.length)}
</a>
<span className="block text-xxs font-normal mb-1">Validated data</span> <span className="block text-xxs font-normal mb-1">Validated data</span>
</div> </div>
<div className="pl-9 pt-[14px]"> <div className="pl-9 pt-[14px]">
@@ -139,15 +170,15 @@ function ProfileTableItem(props) {
Explore -&gt; Explore -&gt;
</a> </a>
</div> </div>
</li> </li>
))} ))}
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>
</td> </td>
</tr> </tr>
</> </>
); );
} }

View File

@@ -1,4 +1,5 @@
import {store} from '../store/store'; import {store} from '../store/store';
import {jsonReplacer} from '../utils/Utils';
function prepareUrl(url) { function prepareUrl(url) {
if (url.startsWith('http')) return url; if (url.startsWith('http')) return url;
@@ -8,13 +9,22 @@ function prepareUrl(url) {
function handleRequest(method, url, headers, attempts, token, body) { function handleRequest(method, url, headers, attempts, token, body) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
(function internalRequest() { (function internalRequest() {
if(store)
store.loading = true;
return request(method, url, headers, token, body) return request(method, url, headers, token, body)
.then(resolve) .then(resolve)
.catch(err => --attempts > 0 ? internalRequest() : reject(err)) .catch(err => --attempts > 0 ? internalRequest() : reject(err))
})() })()
}) })
.then((res) => res.json()) .then((res) => {
.catch(() => []) store.loading = false;
return res.json();
})
.catch((err) => {
store.loading = false;
console.log(err)
return {}
})
}; };
/** /**
@@ -47,14 +57,14 @@ function request(method, url, headers = {}, token, body) {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
...headers ...headers
}, },
body: JSON.stringify(body), body: JSON.stringify(body, jsonReplacer),
signal: controller.signal, signal: controller.signal,
} }
}; };
if (!token) { if (!token) {
requestOptions.headers.Authorization = null; requestOptions.headers.Authorization = null;
} }
setTimeout(() => controller.abort(), 3000); setTimeout(() => controller.abort(), 15000);
return fetch(prepareUrl(url), requestOptions); return fetch(prepareUrl(url), requestOptions);
}; };

7
src/shared/statusMap.js Normal file
View File

@@ -0,0 +1,7 @@
export const statusMap={
completed:'Completed',
incorrect:'Incorrect',
stored: 'Stored',
blockchained: 'Blockchained',
processed: 'Processed'
}

View File

@@ -1,131 +1,202 @@
import {reaction, makeAutoObservable, onBecomeObserved, onBecomeUnobserved} from "mobx"; import { reaction, makeAutoObservable, onBecomeObserved, onBecomeUnobserved } from 'mobx';
import {cryptography} from "@liskhq/lisk-client"; import { cryptography } from '@liskhq/lisk-client';
import { passphrase } from "@liskhq/lisk-client"; import { passphrase } from '@liskhq/lisk-client';
import {getNodeInfo} from '../api/node'; import { getNodeInfo } from '../api/node';
import {fetchWrapper} from '../shared/fetchWrapper'; import { fetchWrapper } from '../shared/fetchWrapper';
import {labelMap} from "../shared/labelMap"; import { labelMap } from '../shared/labelMap';
import {generateSvgAvatar} from "../images/GenerateOnboardingSvg/GenerateSvg"; import { generateSvgAvatar } from '../images/GenerateOnboardingSvg/GenerateSvg';
import {decryptedData} from "../utils/decryptedData";
import { encryptAccountData, generateTransaction, hashAccountData } from '../utils/Utils';
class Store { class Store {
_accountData = [] _accountData = [];
_passPhrase = '' _passPhrase = '';
_nodeInfo = {} _nodeInfo = {};
_keysArray = [] _keysArray = [];
_sharedData = [] _sharedData = [];
_transactionsInfo = [] _transactionsInfo = [];
_accountInfo = {};
_loading = false;
_tempTransactions = [];
constructor() { constructor() {
makeAutoObservable(this, {}); makeAutoObservable(this, {});
this.fetchNodeInfo() reaction(
() => this.keysArray,
() => this.fetchSharedData()
);
reaction(
() => this.address,
() => this.fetchAccountInfo()
);
onBecomeObserved(this, 'decryptedAccountData', () => this.fetchNewAccountData());
onBecomeObserved(this, 'sharedData', () => this.fetchKeysArray());
onBecomeUnobserved(this, 'sharedData', () => this.unobservedSharedData());
onBecomeObserved(this, 'transactionsInfo', () => this.fetchTransactionsInfo());
onBecomeUnobserved(this, 'transactionsInfo', () => this.unobservedTransactionsInfo());
reaction(() => this.keysArray, () => this.fetchSharedData()) this.fetchNodeInfo();
onBecomeObserved(this, "decryptedAccountData", () => this.fetchNewAccountData()) }
onBecomeObserved(this, "sharedData", () => this.fetchKeysArray())
onBecomeUnobserved(this, "sharedData", () => this.unobservedSharedData())
onBecomeObserved(this, "transactionsInfo", () => this.fetchTransactionsInfo())
onBecomeUnobserved(this, "transactionsInfo", () => this.unobservedTransactionsInfo())
};
fetchNewAccountData() { fetchNewAccountData() {
getNodeInfo() getNodeInfo()
.then((info)=>{ .then((info) => {
this.fetchNodeInfoSuccess(info) this.fetchNodeInfoSuccess(info);
fetchWrapper.getAuth('data/private', { fetchWrapper
networkIdentifier: this.nodeInfo.networkIdentifier, .getAuth('data/private', {
lastBlockID: this.nodeInfo.lastBlockID networkIdentifier: this.nodeInfo.networkIdentifier,
}).then((res) => this.accountDataFetchChange(res)) lastBlockID: this.nodeInfo.lastBlockID,
}) })
.catch((err) => this.fetchNodeInfoFailed(err)) .then((res) => this.accountDataFetchChange(res))
.catch((err) => this.fetchError(err));
})
.catch((err) => this.fetchError(err));
} }
pushAccountData(data = this.accountData) { fetchAccountInfo() {
fetchWrapper.postAuth('data/private', { fetchWrapper
networkIdentifier: this.nodeInfo.networkIdentifier, .get(`accounts/${this.address}`)
lastBlockID: this.nodeInfo.lastBlockID .then((res) => this.fetchAccountInfoSuccess(res))
}, [...this.encryptAccountData(data)]) .catch((err) => this.fetchError(err));
.then(() => this.fetchNewAccountData())
} }
fetchKeysArray() { fetchKeysArray() {
getNodeInfo() getNodeInfo()
.then((info)=>{ .then((info) => {
this.fetchNodeInfoSuccess(info) this.fetchNodeInfoSuccess(info);
fetchWrapper.getAuth('data/shared/', { fetchWrapper
networkIdentifier: this.nodeInfo.networkIdentifier, .getAuth('data/shared/', {
lastBlockID: this.nodeInfo.lastBlockID networkIdentifier: this.nodeInfo.networkIdentifier,
}).then((res) => this.keysArrayFetchChange(res)) lastBlockID: this.nodeInfo.lastBlockID,
}) })
.catch((err) => this.fetchNodeInfoFailed(err)) .then((res) => this.keysArrayFetchChange(res));
})
.catch((err) => this.fetchError(err));
} }
fetchTransactionsInfo() { fetchTransactionsInfo() {
fetchWrapper.get(`account/transactions/${this.address}`, { fetchWrapper
.get(`account/transactions/${this.address}`, {
networkIdentifier: this.nodeInfo.networkIdentifier, networkIdentifier: this.nodeInfo.networkIdentifier,
lastBlockID: this.nodeInfo.lastBlockID lastBlockID: this.nodeInfo.lastBlockID,
}).then((res) => this.saveInfoTransactions(res)) })
}; .then((res) => this.saveInfoTransactions(res))
.catch((err) => this.fetchError(err));
}
fetchTempTransactions() {
fetchWrapper
.get('node/transactions')
.then((res) => this.fetchTempTransactionsSuccess(res))
.catch((err) => this.fetchError(err));
}
fetchPassPhrase() {
this._passPhrase = sessionStorage.getItem('passPhrase') || '';
}
fetchSharedData() {
this._keysArray.forEach((elem) =>
fetchWrapper
.get(`data/shared/${elem}`)
.then((res) => this.changeSharedData(res))
.catch((err) => this.fetchError(err))
);
}
fetchNodeInfo() {
getNodeInfo()
.then((info) => this.fetchNodeInfoSuccess(info))
.catch((err) => this.fetchError(err));
}
pushAccountData(data = this.accountData) {
fetchWrapper
.postAuth(
'data/private',
{
networkIdentifier: this.nodeInfo.networkIdentifier,
lastBlockID: this.nodeInfo.lastBlockID,
},
[...encryptAccountData(data, this.passPhrase, this.pubKey)]
)
.then(() => this.fetchNewAccountData())
.catch((err) => this.fetchError(err));
}
pushAccountDataToBlockchain(data = this.decryptedAccountData) {
const [removed, changed] = hashAccountData(data, this.decryptedAccountData, this.accountFeaturesMap);
const builder = generateTransaction(
BigInt(this.accountInfo?.sequence?.nonce || 0),
this.addressAndPubKey.publicKey,
this.nodeInfo.networkIdentifier,
this.passPhrase
);
let signedTx = null;
if (removed.length !== 0) signedTx = builder.remove(removed);
if (changed.length !== 0) signedTx = builder.update(changed);
if (signedTx) {
fetchWrapper.post('transactions', {}, signedTx)
.then(() => this.fetchTempTransactions())
.catch((err) => this.fetchError(err));
this.accountInfo.sequence.nonce++;
}
}
generatePassPhrase() { generatePassPhrase() {
this._passPhrase = passphrase.Mnemonic.generateMnemonic(); this._passPhrase = passphrase.Mnemonic.generateMnemonic();
sessionStorage.setItem('passPhrase', this._passPhrase); sessionStorage.setItem('passPhrase', this._passPhrase);
};
savePastPassPhrase(phrase) {
this._passPhrase=phrase
sessionStorage.setItem('passPhrase', this._passPhrase);
};
fetchPassPhrase() {
this._passPhrase = sessionStorage.getItem('passPhrase') || ''
} }
fetchSharedData() { savePastPassPhrase(phrase) {
this._keysArray.forEach((elem) => fetchWrapper.get(`data/shared/${elem}`).then((res) => this.changeSharedData(res))) this._passPhrase = phrase;
}; sessionStorage.setItem('passPhrase', this._passPhrase);
}
unobservedTransactionsInfo() { unobservedTransactionsInfo() {
this._transactionsInfo = [] this._transactionsInfo = [];
}; }
unobservedSharedData() { unobservedSharedData() {
this._sharedData = [] this._sharedData = [];
}; }
changeSharedData(incomingData) { changeSharedData(incomingData) {
this._sharedData.push({ this._sharedData.push({
data: incomingData.data, data: incomingData.data,
}) });
} }
saveDataRegistration(data) { saveDataRegistration(data) {
this._accountData = data; this._accountData = data;
}; }
clearDataRegistration() { clearDataRegistration() {
this._accountData = []; this._accountData = [];
};
fetchNodeInfo() {
getNodeInfo()
.then((info)=>this.fetchNodeInfoSuccess(info))
.catch((err) => this.fetchNodeInfoFailed(err))
} }
saveInfoTransactions(transaction) { saveInfoTransactions(transaction) {
this._transactionsInfo = transaction.data this._transactionsInfo = transaction.data;
} }
accountDataFetchChange(res) { accountDataFetchChange(res) {
if (res.data) { if (res.data) {
this._accountData = res.data; this._accountData = res.data;
} }
}; this.fetchTempTransactions();
}
keysArrayFetchChange(res) { keysArrayFetchChange(res) {
this._keysArray = res.data; this._keysArray = res.data;
@@ -133,70 +204,67 @@ class Store {
fetchNodeInfoSuccess(info) { fetchNodeInfoSuccess(info) {
this._nodeInfo = info.data; this._nodeInfo = info.data;
this.fetchPassPhrase();
} }
fetchNodeInfoFailed(err) { fetchAccountInfoSuccess(res) {
this._accountInfo = res.data;
if (this.accountInfo?.sequence?.nonce)
this.accountInfo.sequence.nonce = parseInt(this.accountInfo.sequence.nonce) || 0;
}
fetchTempTransactionsSuccess(res) {
this._tempTransactions = res.data;
}
fetchError(err) {
console.log(err); console.log(err);
} }
get sharedData() { get sharedData() {
return this._sharedData.map(elem => ({ return this._sharedData.map((elem) => ({
data: elem.data.map(item => ({ data: elem.data.map((item) => ({
label: labelMap[item.label], label: labelMap[item.label],
value: item.value, value: item.value,
})) })),
})); }));
}; }
get transactionsInfo() { get transactionsInfo() {
return this._transactionsInfo.map(item => { return this._transactionsInfo.map((item) => {
return { return {
id: item.id, id: item.id,
sender_avatar: item.asset.recipientAddress && generateSvgAvatar(item.senderPublicKey), sender_avatar: item.asset.recipientAddress && generateSvgAvatar(item.senderPublicKey),
avatar_size: 24, avatar_size: 24,
transaction: item.asset.features.map(asset => { transaction: item.asset.features.map((asset) => {
return { return {
transaction_id: item.id, transaction_id: item.id,
address: item.asset.recipientAddress && cryptography.bufferToHex(cryptography.getAddressFromPublicKey(cryptography.hexToBuffer(item.senderPublicKey))), address:
item.asset.recipientAddress &&
cryptography.bufferToHex(
cryptography.getAddressFromPublicKey(cryptography.hexToBuffer(item.senderPublicKey))
),
value: asset.value, value: asset.value,
label: labelMap[asset.label] || asset.label label: labelMap[asset.label] || asset.label,
} };
}) }),
} };
}) });
} }
get decryptedAccountData() { get decryptedAccountData() {
return this._accountData.map((elem) => ({ return decryptedData(this._accountData, this.accountFeatures, this.passPhrase, this.pubKey)
...elem,
key: elem.label,
label: labelMap[elem.label] || elem.label,
value: cryptography.decryptMessageWithPassphrase(elem.value, elem.value_nonce, this.passPhrase, this.pubKey).split(':')[1]
}))
}
encryptAccountData(data) {
return data.map((item) => {
let value = cryptography.encryptMessageWithPassphrase(cryptography.getRandomBytes(32).toString('hex') + ":" + item.value, this.passPhrase, this.pubKey);
return {
label: item.key,
value: value.encryptedMessage,
value_nonce: value.nonce,
seed: item.seed,
}
})
} }
get firstName() { get firstName() {
return this.decryptedAccountData.find(item => item.key === 'firstname')?.value return this.decryptedAccountData.find((item) => item.key === 'firstname')?.value;
} }
get lastName() { get lastName() {
return this.decryptedAccountData.find(item => item.key === 'secondname')?.value return this.decryptedAccountData.find((item) => item.key === 'secondname')?.value;
} }
get accountName() { get accountName() {
return this.firstName || this.lastName || this.pubKey return this.firstName || this.lastName || this.pubKey;
} }
get keysArray() { get keysArray() {
@@ -204,13 +272,16 @@ class Store {
} }
get passPhrase() { get passPhrase() {
this.fetchPassPhrase() return this._passPhrase;
return this._passPhrase
} }
get accountData() { get accountData() {
return this._accountData return this._accountData;
}; }
get accountInfo() {
return this._accountInfo;
}
get nodeInfo() { get nodeInfo() {
return this._nodeInfo; return this._nodeInfo;
@@ -225,15 +296,56 @@ class Store {
} }
get address() { get address() {
return this.addressAndPubKey.address.toString('hex') return this.addressAndPubKey.address.toString('hex');
} }
get tokenKey() { get tokenKey() {
const stringToSign = this.nodeInfo.networkIdentifier + this.nodeInfo.lastBlockID; const stringToSign = this.nodeInfo.networkIdentifier + this.nodeInfo.lastBlockID;
if(!stringToSign) if (!stringToSign) return '';
return ''; const sign = cryptography.signDataWithPassphrase(Buffer.from(stringToSign, 'hex'), this.passPhrase).toString('hex');
const sign = cryptography.signDataWithPassphrase(Buffer.from(stringToSign, 'hex'), this.passPhrase).toString('hex') return this.pubKey + ':' + sign;
return this.pubKey + ':' +sign }
get accountIdentity() {
return this.accountInfo?.identity || {};
}
get accountFeatures() {
return this.accountIdentity?.features || [];
}
get accountFeaturesMap() {
return this.accountFeatures.reduce(
(acc, item) => ({
...acc,
[item.label]: item.value,
}),
{}
);
}
get loading() {
return this._loading;
}
set loading(value) {
this._loading = value;
}
get tempTransactions() {
return this._tempTransactions;
}
get processedFeatures() {
return this.tempTransactions
.filter(item => item.senderPublicKey === this.pubKey)
.reduce((acc, item) => {
const { features } = item.asset;
return {
...acc,
...features.reduce((obj, feature) => ({...obj, [feature.label]: true}), {})
}
}, {})
} }
} }

127
src/utils/Schemas.js Normal file
View File

@@ -0,0 +1,127 @@
export const IdentityModuleSchema = {
$id: 'idntty/identity/module',
title: 'Identity module account schema',
type: 'object',
properties: {
features: {
fieldNumber: 1,
type: 'array',
maxItems: 256,
items: {
type: 'object',
required: ['label', 'value'],
properties: {
label: { fieldNumber: 1, dataType: 'string'},
value: { fieldNumber: 2, dataType: 'bytes'},
}
}
},
verifications: {
fieldNumber: 2,
type: 'array',
items: {
type: 'object',
required: ['label', 'account', 'tx'],
properties: {
label: { fieldNumber: 1, dataType: 'string'},
account: { fieldNumber: 2, dataType: 'bytes'},
tx: { fieldNumber: 3, dataType: 'bytes'},
}
}
},
},
default: {features: [], verifications: []}
};
export const setFeatureAssetSchema = {
$id: 'idntty/identity/setfeature',
title: 'Asset schema to set or update account features for identity module',
type: 'object',
required: ['features'],
properties: {
features: {
fieldNumber: 1,
type: 'array',
minItems: 1,
maxItems: 16,
items: {
type: 'object',
required: ['label','value'],
properties: {
label: { fieldNumber: 1, dataType: 'string', maxLength: 16},
value: { fieldNumber: 2, dataType: 'bytes', maxLength: 32},
}
}
}
}
};
export const removeFeatureAssetSchema = {
$id: 'idntty/identity/removefeature',
title: 'Asset schema to remove account features for identity module',
type: 'object',
required: ['features'],
properties: {
features: {
fieldNumber: 1,
type: 'array',
minItems: 1,
maxItems: 16,
items: {
type: 'object',
required: ['label'],
properties: {
label: { fieldNumber: 1, dataType: 'string', maxLength: 16},
}
}
}
}
};
export const validateFeatureAssetSchema = {
$id: 'idntty/identity/validatefeature',
title: 'Asset schema to validate account features for identity module',
type: 'object',
required: ['recipientAddress','features'],
properties: {
recipientAddress: {fieldNumber: 1, dataType: 'bytes', minLength: 20, maxLength: 20},
features: {
fieldNumber: 2,
type: 'array',
minItems: 1,
maxItems: 16,
items: {
type: 'object',
required: ['label', 'value'],
properties: {
label: { fieldNumber: 1, dataType: 'string', maxLength: 16},
value: { fieldNumber: 2, dataType: 'bytes', maxLength: 32},
}
}
}
}
};
export const invalidateFeatureAssetSchema = {
$id: 'idntty/identity/invalidatefeature',
title: 'Asset schema to invalidate account features for identity module',
type: 'object',
required: ['recipientAddress','features'],
properties: {
recipientAddress: {fieldNumber: 1, dataType: 'bytes', minLength: 20, maxLength: 20},
features: {
fieldNumber: 2,
type: 'array',
minItems: 1,
maxItems: 16,
items: {
type: 'object',
required: ['label'],
properties: {
label: { fieldNumber: 1, dataType: 'string', maxLength: 16},
}
}
}
}
};

View File

@@ -1,4 +1,8 @@
import resolveConfig from 'tailwindcss/resolveConfig'; import resolveConfig from 'tailwindcss/resolveConfig';
import {cryptography, transactions} from '@liskhq/lisk-client';
import {removeFeatureAssetSchema, setFeatureAssetSchema} from './Schemas';
import {statusMap} from "../shared/statusMap";
import {labelMap} from "../shared/labelMap";
export const tailwindConfig = () => { export const tailwindConfig = () => {
// Tailwind config // Tailwind config
@@ -32,3 +36,123 @@ export const formatThousands = (value) => Intl.NumberFormat('en-US', {
maximumSignificantDigits: 3, maximumSignificantDigits: 3,
notation: 'compact', notation: 'compact',
}).format(value); }).format(value);
export const encryptAccountData = (data = [], passPhrase = '', pubKey = '') => {
return data.filter(item=>item.status!==statusMap.blockchained).map((item) => {
let value = cryptography.encryptMessageWithPassphrase(item.seed + ":" + item.value, passPhrase, pubKey);
return {
label: item.key,
value: value.encryptedMessage,
value_nonce: value.nonce,
seed: item.seed,
}
})
}
export const hashAccountData = (data = [], oldData = [], hashMap = {}) => {
const accountMap = data.reduce((acc, item) => ({
...acc,
[item.label]: item.value
}), {})
const oldAccountMap = oldData.reduce((acc, item) => ({
...acc,
[item.label]: item.value
}), {})
const removed = oldData.reduce((acc, item) => {
if(!accountMap[item.label] && !data.find(elem=>elem.label===item.label))
return [...acc, {label: item.label}];
return acc
}, [])
const changed = data.reduce((acc, item) => {
if(!oldAccountMap[item.label])
return [ ...acc, item ]
const oldValue = hashMap[item.key];
const newValue = hashValue(item.value, item.seed).toString('hex')
if(oldValue !== newValue && item.status==='new')
return [ ...acc, item ]
return acc;
}, [])
return [
removed,
changed.map((item) => {
const value = hashValue(item.value, item.seed);
return {
label: item.key,
value
}
})
]
}
export const jsonReplacer = (key, value) => {
if (key === 'big') {
return value.toString();
}
return value;
}
export const hashValue = (value = '', seed = '') => cryptography.hash(Buffer.concat([Buffer.from(seed, 'utf8'), cryptography.hash(value, 'utf8')]));
export const generateTransaction = (nonce = BigInt(0), senderPublicKey = '', networkIdentifier = '', passPhrase = '', fee = BigInt(500000),) => {
return {
update: (features) => generateSetTransaction(features, nonce, senderPublicKey, networkIdentifier, passPhrase, fee),
remove: (features) => generateRemoveTransaction(features, nonce, senderPublicKey, networkIdentifier, passPhrase, fee),
}
}
export const generateSetTransaction = (features, nonce = BigInt(0), senderPublicKey = '', networkIdentifier = '', passPhrase = '', fee = BigInt(500000)) => {
const tx = {
"moduleID": 1001,
"assetID": 1,
nonce,
senderPublicKey,
fee,
"asset": {
features
},
}
if(features.length === 0)
return;
const signedTx = transactions.signTransaction(setFeatureAssetSchema,
tx, Buffer.from(networkIdentifier, "hex"),
passPhrase)
signedTx.senderPublicKey = signedTx.senderPublicKey.toString("hex");
signedTx.signatures[0] = signedTx.signatures[0].toString("hex");
signedTx.asset.features = signedTx.asset.features.map(feature => ({
...feature,
value: feature.value.toString("hex")
}))
delete signedTx.id;
return signedTx;
}
export const generateRemoveTransaction = (features, nonce = BigInt(0), senderPublicKey = '', networkIdentifier = '', passPhrase = '', fee = BigInt(500000)) => {
const tx = {
"moduleID": 1001,
"assetID": 2,
nonce,
senderPublicKey,
fee,
"asset": {
features
},
}
const signedTx = transactions.signTransaction(removeFeatureAssetSchema,
tx, Buffer.from(networkIdentifier, "hex"),
passPhrase)
signedTx.senderPublicKey = signedTx.senderPublicKey.toString(
"hex");
signedTx.signatures[0] = signedTx.signatures[0].toString("hex");
delete signedTx.id;
return signedTx;
}

View File

@@ -0,0 +1,48 @@
import {labelMap} from "../shared/labelMap";
import {statusMap} from "../shared/statusMap";
import {cryptography} from "@liskhq/lisk-client";
export const decryptedData=(serverData, blockchainData, passPhrase, pubKey)=>{
const allAccountData=serverData.concat(blockchainData.filter(item=>!serverData.find(elem=>elem.label===item.label)))
return allAccountData.map((elem) => {
const initialData={
...elem,
key: elem.label,
label: labelMap[elem.label] || elem.label,
status: '',
value: '',
seed: ''
}
if(!serverData.find(item=>item.label===elem.label)) {
return {
...initialData,
status: statusMap.blockchained,
value: elem.value
}
}
const [seed, value] = cryptography.decryptMessageWithPassphrase(elem.value, elem.value_nonce, passPhrase, pubKey).split(':');
const hashValue=cryptography.hash(Buffer.concat([Buffer.from(seed, 'utf8'), cryptography.hash(value, 'utf8')])).toString('hex')
if(!blockchainData.find(item=>item.label===elem.label)) {
return {
...initialData,
status: statusMap.stored,
value,
seed
}
}
if(blockchainData.find(item=>item.label===elem.label).value!==hashValue) {
return {
...initialData,
status: statusMap.incorrect,
value,
seed
}
}
return {
...initialData,
status: statusMap.completed,
value,
seed
}
})
}