add unlock transaction

This commit is contained in:
kandrusyak
2022-12-09 11:53:36 +03:00
parent 68e665804c
commit 5939bb4e13
6 changed files with 80 additions and 137 deletions

View File

@@ -6,6 +6,7 @@
"endOfLine": "lf",
"jsxBracketSameLine": false,
"trailingComma": "es5",
"printWidth": 100,
"overrides": [
{
"files": "*.json",

View File

@@ -81,7 +81,11 @@ const Delegates = observer(() => {
<TransactionsTable
data={delegates}
lockedFor={lockedFor}
rowClick={(delegate) => setCurrentDelegate(delegate)}
rowClick={(delegate) =>
(lockedFor === 'Voiting' ||
store.accountLockedVotesCanReturnSum[delegate.address]) &&
setCurrentDelegate(delegate)
}
/>
{/*/!* Pagination *!/*/}
@@ -99,9 +103,11 @@ const Delegates = observer(() => {
transactionPanelOpen={!!currentDelegate}
onClose={() => setCurrentDelegate(null)}
delegate={currentDelegate || {}}
postTransaction={(address, amount) =>
store.pushVoteTransaction(address, amount)
}
unlocking={lockedFor !== 'Voiting'}
postTransaction={(address, amount) => {
if (lockedFor === 'Voiting') store.pushVoteTransaction(address, amount);
else store.pushUnlockTransaction(address);
}}
/>
</div>
</main>

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from 'react';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import Image from '../../images/transactions-image-04.svg';
import moment from 'moment';
@@ -9,16 +9,19 @@ import UserAvatar from '../../images/user-avatar-32.png';
import { observer } from 'mobx-react-lite';
const TransactionPanel = observer(
({ transactionPanelOpen, onClose, delegate, postTransaction }) => {
({ transactionPanelOpen, onClose, delegate, postTransaction, unlocking }) => {
const closeBtn = useRef(null);
const panelContent = useRef(null);
const [amount, setAmount] = useState(0);
const status = useMemo(() => (unlocking ? 'Unlock' : delegate.status), [unlocking, delegate]);
const statusColor = () => {
switch (delegate.status) {
switch (status) {
case 'Vote':
return 'bg-emerald-100 text-emerald-600';
case 'Unlock':
case 'Unvote':
return 'bg-rose-100 text-rose-500';
default:
@@ -26,8 +29,7 @@ const TransactionPanel = observer(
}
};
const amountLabel =
delegate.status === 'Vote' ? 'Voiting amount' : 'Unvoiting amount';
const amountLabel = delegate.status === 'Vote' ? 'Voiting amount' : 'Unvoiting amount';
// close if the esc key is pressed
useEffect(() => {
@@ -40,12 +42,13 @@ const TransactionPanel = observer(
});
useEffect(() => {
const status = delegate.status;
if (status === 'Unvote') {
setAmount(store.accountSentVotes[delegate.address].toString());
}
}, [delegate]);
if (status === 'Unlock') {
setAmount(store.accountLockedVotesCanReturnSum[delegate.address]?.toString());
}
}, [delegate, status]);
return (
<div
@@ -70,9 +73,7 @@ const TransactionPanel = observer(
</button>
<div className="py-8 px-4 lg:px-8">
<div className="max-w-sm mx-auto lg:max-w-none">
<div className="text-slate-800 font-semibold text-center mb-1">
Vote Transaction
</div>
<div className="text-slate-800 font-semibold text-center mb-1">Vote Transaction</div>
<div className="text-sm text-center italic">
{moment().format('DD/MM/YYYY hh:mm A')}
</div>
@@ -89,40 +90,28 @@ const TransactionPanel = observer(
alt="Transaction 04"
/>
</div>
<div
className={`text-2xl font-semibold mb-1 ${statusColor()} bg-transparent`}
>
<div className={`text-2xl font-semibold mb-1 ${statusColor()} bg-transparent`}>
{amount}/idn
</div>
<div className="text-sm font-medium text-slate-800 mb-3">
{delegate?.dpos?.delegate?.username ||
formatAddressBig(delegate.address || '')}
{delegate?.dpos?.delegate?.username || formatAddressBig(delegate.address || '')}
</div>
<div
className={`text-xs inline-flex font-medium rounded-full text-center px-2.5 py-1 ${statusColor()} justify-center`}
style={{ minWidth: '84px' }}
>
{delegate.status}
{status}
</div>
</div>
{/* Divider */}
<div
className="flex justify-between items-center"
aria-hidden="true"
>
<svg
className="w-5 h-5 fill-white"
xmlns="http://www.w3.org/2000/svg"
>
<div className="flex justify-between items-center" aria-hidden="true">
<svg className="w-5 h-5 fill-white" xmlns="http://www.w3.org/2000/svg">
<path d="M0 20c5.523 0 10-4.477 10-10S5.523 0 0 0h20v20H0Z" />
</svg>
<div className="grow w-full h-5 bg-white flex flex-col justify-center">
<div className="h-px w-full border-t border-dashed border-slate-200" />
</div>
<svg
className="w-5 h-5 fill-white rotate-180"
xmlns="http://www.w3.org/2000/svg"
>
<svg className="w-5 h-5 fill-white rotate-180" xmlns="http://www.w3.org/2000/svg">
<path d="M0 20c5.523 0 10-4.477 10-10S5.523 0 0 0h20v20H0Z" />
</svg>
</div>
@@ -136,9 +125,7 @@ const TransactionPanel = observer(
</div>
<div className="flex justify-between space-x-1">
<span className="italic">FEE:</span>
<span className="font-medium text-slate-700 text-right">
145/idn
</span>
<span className="font-medium text-slate-700 text-right">145/idn</span>
</div>
<div className="flex justify-between space-x-1">
<span className="italic">Nonce:</span>
@@ -156,11 +143,9 @@ const TransactionPanel = observer(
</div>
{/* Receipts */}
<div className="mt-6">
<label
className="block text-sm font-medium mb-1"
htmlFor="mandatory"
>
{amountLabel} <span className="text-rose-500">*</span>
<label className="block text-sm font-medium mb-1" htmlFor="mandatory">
{unlocking ? 'Unlock amount' : amountLabel}{' '}
<span className="text-rose-500">*</span>
</label>
<input
id="amount"
@@ -169,13 +154,12 @@ const TransactionPanel = observer(
required
onChange={(e) => setAmount(e.target.value)}
value={amount}
disabled={unlocking}
/>
</div>
{/* Notes */}
<div className="mt-6">
<div className="text-sm font-semibold text-slate-800 mb-2">
Transaction details
</div>
<div className="text-sm font-semibold text-slate-800 mb-2">Transaction details</div>
<div className="flex p-2 justify-between items-center border border-slate-300 rounded-md">
<div className="flex gap-2">
<img
@@ -185,9 +169,7 @@ const TransactionPanel = observer(
height="32"
alt="User"
/>
<div className="flex items-center text-sm text-slate-700">
Balance
</div>
<div className="flex items-center text-sm text-slate-700">Balance</div>
</div>
<div className="flex items-center text-sm text-slate-700">
{store.accountBalance}/idn

View File

@@ -47,22 +47,16 @@ const TransactionsTable = observer(({ data, rowClick, lockedFor }) => {
balance={delegate?.token?.balance}
name={delegate.dpos?.delegate?.username || delegate.address}
total={delegate.dpos?.delegate?.totalVotesReceived || ''}
hasSevice={
delegate.dpos?.delegate?.username === 'delegate_0'
}
hasSevice={delegate.dpos?.delegate?.username === 'delegate_0'}
date={delegate.date}
displayReturn={lockedFor === 'Unlocking'}
status={delegate.status}
amount={
(lockedFor === 'Voiting'
? store.accountSentVotes[delegate.address]
: store.accountLockedVotesCanReturnSum[
delegate.address
]) || ''
}
handleClick={() =>
lockedFor === 'Voiting' && rowClick(delegate)
: store.accountLockedVotesCanReturnSum[delegate.address]) || ''
}
handleClick={() => rowClick(delegate)}
/>
);
})}

View File

@@ -24,11 +24,6 @@ const TransactionsTableItem = observer((props) => {
}
};
const clickReturn = (e) => {
e.stopPropagation();
store.pushUnlockTransaction(props.id);
};
return (
<tr className="cursor-pointer" onClick={() => props.handleClick()}>
<td className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap md:w-1/2">
@@ -71,14 +66,14 @@ const TransactionsTableItem = observer((props) => {
</td>
<td className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
<div className="text-center">
{props.displayReturn ? (
{props.displayReturn && props.status !== 'Pending' ? (
store.accountLockedVotesCanReturn[props.id] && (
<span
className="truncate font-medium text-indigo-500 group-hover:text-indigo-600 ml-2"
onClick={clickReturn}
<div
className={`text-xs inline-flex font-medium rounded-full justify-center px-2.5 py-1 bg-rose-100 text-rose-500`}
style={{ minWidth: '84px' }}
>
Return votes
</span>
Unlock
</div>
)
) : (
<div

View File

@@ -1,9 +1,4 @@
import {
reaction,
makeAutoObservable,
onBecomeObserved,
onBecomeUnobserved,
} from 'mobx';
import { reaction, makeAutoObservable, onBecomeObserved, onBecomeUnobserved } from 'mobx';
import { cryptography, transactions } from '@liskhq/lisk-client';
import { passphrase } from '@liskhq/lisk-client';
import { getNodeInfo } from '../api/node';
@@ -67,20 +62,12 @@ class Store {
() => this.address,
() => this.fetchAccountInfo()
);
onBecomeObserved(this, 'decryptedAccountData', () =>
this.fetchNewAccountData()
);
onBecomeUnobserved(this, 'decryptedAccountData', () =>
this.unobservedAccountData()
);
onBecomeObserved(this, 'decryptedAccountData', () => this.fetchNewAccountData());
onBecomeUnobserved(this, 'decryptedAccountData', () => this.unobservedAccountData());
onBecomeObserved(this, 'sharedData', () => this.fetchKeysArray());
onBecomeUnobserved(this, 'sharedData', () => this.unobservedSharedData());
onBecomeObserved(this, 'transactionsInfo', () =>
this.fetchTransactionsInfo()
);
onBecomeUnobserved(this, 'transactionsInfo', () =>
this.unobservedTransactionsInfo()
);
onBecomeObserved(this, 'transactionsInfo', () => this.fetchTransactionsInfo());
onBecomeUnobserved(this, 'transactionsInfo', () => this.unobservedTransactionsInfo());
this.fetchNodeInfo();
}
@@ -342,8 +329,7 @@ class Store {
fetchAccountInfoSuccess(res) {
this._accountInfo = res.data;
if (this.accountInfo?.sequence?.nonce)
this.accountInfo.sequence.nonce =
parseInt(this.accountInfo.sequence.nonce) || 0;
this.accountInfo.sequence.nonce = parseInt(this.accountInfo.sequence.nonce) || 0;
}
fetchTempTransactionsSuccess(res) {
@@ -370,24 +356,23 @@ class Store {
.map((item) => {
return {
id: item.id,
sender_avatar:
item.asset.recipientAddress &&
generateSvgAvatar(item.senderPublicKey),
sender_avatar: item?.asset?.recipientAddress && generateSvgAvatar(item.senderPublicKey),
avatar_size: 24,
transaction: item.asset.features.map((asset) => {
return {
transaction_id: item.id,
address:
item.asset.recipientAddress &&
cryptography.bufferToHex(
cryptography.getAddressFromPublicKey(
cryptography.hexToBuffer(item.senderPublicKey)
)
),
value: asset.value,
label: labelMap[asset.label] || asset.label,
};
}),
transaction:
item.asset?.features?.map((asset) => {
return {
transaction_id: item.id,
address:
item.asset.recipientAddress &&
cryptography.bufferToHex(
cryptography.getAddressFromPublicKey(
cryptography.hexToBuffer(item.senderPublicKey)
)
),
value: asset.value,
label: labelMap[asset.label] || asset.label,
};
}) || [],
};
});
}
@@ -402,13 +387,11 @@ class Store {
}
get firstName() {
return this.decryptedAccountData.find((item) => item.key === 'firstname')
?.value;
return this.decryptedAccountData.find((item) => item.key === 'firstname')?.value;
}
get lastName() {
return this.decryptedAccountData.find((item) => item.key === 'secondname')
?.value;
return this.decryptedAccountData.find((item) => item.key === 'secondname')?.value;
}
get accountName() {
@@ -440,16 +423,12 @@ class Store {
}
get privateKey() {
return cryptography.getPrivateAndPublicKeyFromPassphrase(this.passPhrase)
?.privateKey;
return cryptography.getPrivateAndPublicKeyFromPassphrase(this.passPhrase)?.privateKey;
}
get vpnPrivateKey() {
let x25519_sk = Buffer.alloc(sodium.crypto_box_SECRETKEYBYTES);
sodium.crypto_sign_ed25519_sk_to_curve25519(
x25519_sk,
Buffer.from(this.privateKey, 'hex')
);
sodium.crypto_sign_ed25519_sk_to_curve25519(x25519_sk, Buffer.from(this.privateKey, 'hex'));
return x25519_sk.toString('Base64');
}
@@ -462,8 +441,7 @@ class Store {
}
get tokenKey() {
const stringToSign =
this.nodeInfo.networkIdentifier + this.nodeInfo.lastBlockID;
const stringToSign = this.nodeInfo.networkIdentifier + this.nodeInfo.lastBlockID;
if (!stringToSign) return '';
const sign = cryptography
.signDataWithPassphrase(Buffer.from(stringToSign, 'hex'), this.passPhrase)
@@ -480,17 +458,14 @@ class Store {
}
get accountBalance() {
return (
BigInt(this.accountInfo?.token?.balance || 0) / 100000000n
).toString();
return (BigInt(this.accountInfo?.token?.balance || 0) / 100000000n).toString();
}
get accountSentVotes() {
return (this.accountInfo?.dpos?.sentVotes || []).reduce(
(acc, e) => ({
...acc,
[e.delegateAddress]:
BigInt(e.amount || 0) / 100000000n + (acc[e.delegateAddress] || 0n),
[e.delegateAddress]: BigInt(e.amount || 0) / 100000000n + (acc[e.delegateAddress] || 0n),
}),
{}
);
@@ -500,8 +475,7 @@ class Store {
return (this.accountInfo?.dpos?.unlocking || []).reduce(
(acc, e) => ({
...acc,
[e.delegateAddress]:
BigInt(e.amount || 0) / 100000000n + (acc[e.delegateAddress] || 0n),
[e.delegateAddress]: BigInt(e.amount || 0) / 100000000n + (acc[e.delegateAddress] || 0n),
}),
{}
);
@@ -563,10 +537,7 @@ class Store {
const { features } = item.asset;
return {
...acc,
...(features?.reduce(
(obj, feature) => ({ ...obj, [feature.label]: true }),
{}
) || {}),
...(features?.reduce((obj, feature) => ({ ...obj, [feature.label]: true }), {}) || {}),
};
}, {});
}
@@ -582,10 +553,7 @@ class Store {
(obj, vote) => ({
...obj,
[vote.delegateAddress]:
BigInt(
Number(vote.amount) > 0 ? vote.amount : vote.amount.slice(1)
) /
100000000n +
BigInt(Number(vote.amount) > 0 ? vote.amount : vote.amount.slice(1)) / 100000000n +
(obj[vote.delegateAddress] || 0n),
}),
{}
@@ -605,8 +573,7 @@ class Store {
(obj, vote) => ({
...obj,
[vote.delegateAddress]:
BigInt(vote.amount) / 100000000n +
(obj[vote.delegateAddress] || 0n),
BigInt(vote.amount) / 100000000n + (obj[vote.delegateAddress] || 0n),
}),
{}
) || {}),
@@ -617,9 +584,7 @@ class Store {
get vpnServers() {
return this._vpnServers?.map((server) => ({
...server,
transferSum: formatBytes(
Number(server.transferTx || 0) + Number(server.transferRx || 0)
),
transferSum: formatBytes(Number(server.transferTx || 0) + Number(server.transferRx || 0)),
trafficUsed: Math.floor(
((Number(server.transferTx || 0) + Number(server.transferRx || 0)) /
(50 * 1024 * 1024 * 1024)) *
@@ -638,7 +603,7 @@ class Store {
const getStatus = (delegate) => {
const { address } = delegate;
let amount = this.processedVotes[address];
let amount = this.processedVotes[address] || this.processedUnlocking[address];
if (amount) return 'Pending';