Skip to content

Commit

Permalink
Tx should happen when InBlock and not wait until Block IsFinalized (#95)
Browse files Browse the repository at this point in the history
fixes #46, #101, #102 

TransactionHandler saves tx when InBlock and not when Finalized to get faster UX.

block hash and tx hash are displayed with link to subscan and copy button to copy hash to clipboard.

Remarks: 
1. I changed the font-size to 14 and removed the margin from the left of the status text (InBlock/Finalized + block/tx hashes), so that all the text fits in the window. Else, part of the hash is out of the window bound and you need to switch slide to the youtube slide to see the rest of the hash. 
4. NEW: I have now added an error message to the status bar when a tx failed with its reasoning. Please add your remark if you want the error output differently or any other remark :)
5. ALSO NEW: I have added the display of the estimated fee. I fixed now a first estimated fee value for 0.1 ksm contribution (its 42.6662 µKSM). I'm not sure if this can change dramatically in the future, but when another value is entered, the estimated fee will be calculated. 

Inputting the estimated fee into the calculation of the available balance is somewhat problematic. the reason is, to get the estimate, we make a dummy contribution call "api.tx.crowdloan.contribute(paraId, data.value * Math.pow(10, 12), null).paymentInfo(sender)"
The problem is now, if somebody enters a large contribution value (above 1000 or 10000 ksm), the dummy call overflows since 10^12 * 10^4 is too large and the page crashes.
  • Loading branch information
anizeani authored Jan 3, 2022
1 parent 6b6805a commit c269fdd
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 33 deletions.
23 changes: 19 additions & 4 deletions src/Participate.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, { useState, useEffect, useMemo } from 'react';

import './css/App.css';
import {
Container,
Expand Down Expand Up @@ -53,6 +52,7 @@ export default function Participate (props) {

const [accountAddress, setAccountAddress] = useState(null);
const [accountBalance, setAccountBalance] = useState(0);
const [estimatedFee, setEstimatedFee] = useState('42.3329 µKSM');
const minimumParticipation = 100000000000; // 0.1
const divide = 1000000000000;

Expand Down Expand Up @@ -116,16 +116,27 @@ export default function Participate (props) {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const onChange = (_, data) => {
const onChange = async (_, data) => {
setFormState((prev) => ({ ...prev, [data.state]: data.value }));
// let estimate = 0;
if (!crowdLoanEnded) {
if (data.value >= 0.1) {
try {
const txExcecuteDummy = api.tx.crowdloan.contribute(paraId, data.value * Math.pow(10, 12), null);
const info = await txExcecuteDummy.paymentInfo(accountAddress);
setEstimatedFee(() => info.partialFee.toHuman());
} catch (error) {
console.log(error);
}
// estimate = parseInt(info.partialFee);
}
if (accountBalance < minimumParticipation) {
setDisableButton(true);
setStatus('You do not have enough balance');
} else if (data.value === '' || data.value < minimumParticipation / divide) {
setDisableButton(true);
setStatus('Please enter amount equal or greater than ' + minimumParticipation / divide);
} else if (data.value > (accountBalance / divide)) {
} else if (data.value > accountBalance / divide) {
setDisableButton(true);
setStatus('Please enter amount equal or less than ' + formatBalance(accountBalance));
} else {
Expand Down Expand Up @@ -389,7 +400,11 @@ export default function Participate (props) {
</Grid.Column>
</Grid.Row>
</Grid>

<Grid.Column>
<div>
Estimated fees: {estimatedFee} <br /> Please make sure when contributing, that your balance can cover the fees
</div>
</Grid.Column>
<TxButton
setLoading={setLoading}
accountAddress={accountAddress}
Expand Down
3 changes: 2 additions & 1 deletion src/css/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -1631,7 +1631,8 @@ div[disabled]
}
.polkadot_status{
margin-top: 5px;
margin-left: 40px;
/*margin-left: 40px;*/
font-size: 14px;
}
@media screen and (max-width: 992px) {
.why .text {
Expand Down
74 changes: 46 additions & 28 deletions src/substrate-lib/components/TxButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ function TxButton ({
const { palletRpc, callable, inputParams, paramFields, disableButton } =
attrs;

let txxHash = null;
let contributionError = null;

const isQuery = () => type === 'QUERY';
const isSudo = () => type === 'SUDO-TX';
const isUncheckedSudo = () => type === 'UNCHECKED-SUDO-TX';
Expand Down Expand Up @@ -68,8 +71,8 @@ function TxButton ({
return fromAcct;
};

const txResHandlerSaveTransaction = (status) => {
const hash = status.asFinalized.toString();
const txResHandlerSaveTransaction = async (status) => {
const blockHash = status.asInBlock.toString();
if (isSigned()) {
if (document.getElementById('grc') && document.getElementById('erc')) {
saveParticipateInfo(
Expand All @@ -79,7 +82,7 @@ function TxButton ({
document.getElementById('erc')
? document.getElementById('erc').value
: new URL(window.location.href).searchParams.get('ref'),
hash
blockHash
);
} else if (document.getElementById('erc')) {
saveParticipateInfo(
Expand All @@ -89,21 +92,51 @@ function TxButton ({
document.getElementById('erc')
? document.getElementById('erc').value
: new URL(window.location.href).searchParams.get('ref'),
hash
blockHash
);
} else if (document.getElementById('grc')) {
saveParticipateInfo(accountAddress, formState, document.getElementById('grc').value, '', hash);
saveParticipateInfo(accountAddress, formState, document.getElementById('grc').value, '', blockHash);
}
setLoading(false);
}
setStatus(`😉 Finalized. Block hash: ${hash}`);
setStatus(viewTransactionInfo(status));
};

const txResHandler = ({ status }) => {
status.isFinalized
const viewTransactionInfo = (status) => {
const _blockhash = (status.type === 'InBlock') ? status.asInBlock.toString() : status.asFinalized.toString();
return (
<p>
{contributionError ? `Transaction failed: ${contributionError}` : ''}
<br/>
😉 {status.type}. Block hash: {_blockhash} <Button icon='copy' onClick={() => { navigator.clipboard.writeText(_blockhash); }}/> <br/>
You can get more details on your transaction: <a href={`https://kusama.subscan.io/extrinsic/${txxHash}`}>{txxHash}</a> <Button icon='copy' onClick={() => { navigator.clipboard.writeText(txxHash); }}/>
</p>
);
};

const txResHandler = ({ status, dispatchError }) => {
// status would still be set, but in the case of error we can shortcut
// to just check it (so an error would indicate InBlock or Finalized)
if (dispatchError) {
if (dispatchError.isModule) {
// for module errors, we have the section indexed, lookup
const decoded = api.registry.findMetaError(dispatchError.asModule);
const { docs, name, section } = decoded;
console.log(`${section}.${name}: ${docs.join(' ')}`);
contributionError = `${section}.${name}: ${docs.join(' ')}`;
} else {
// Other, CannotLookup, BadOrigin, no extra info
console.log(dispatchError.toString());
contributionError = dispatchError.toString();
}
}
status.isInBlock
? txResHandlerSaveTransaction(status)
: setStatus(`Current transaction status: ${status.type}`);
: status.isFinalized
? setStatus(viewTransactionInfo(status))
: setStatus(`Current transaction status: ${status.type}`);
};

const txErrHandler = (err) => {
setStatus(`😞 Transaction Failed: ${err.toString()}`);
if (isSigned()) {
Expand Down Expand Up @@ -148,11 +181,11 @@ function TxButton ({
setUnsub(() => unsub);
};

const saveParticipateInfo = (accountAddress, formState, grc, erc, hash) => {
const saveParticipateInfo = (accountAddress, formState, grc, erc, blockHash) => {
const formdata = new FormData();
formdata.append('Participant[email]', grc);
formdata.append('Participant[referrer_code]', erc);
formdata.append('Participant[block_hash]', hash);
formdata.append('Participant[block_hash]', blockHash);
formdata.append('Participant[account_nr]', accountAddress);
formdata.append('Participant[amount]', formState.amount);

Expand All @@ -166,55 +199,40 @@ function TxButton ({
};

fetch('https://api.crowdloan.integritee.network/storeuser', requestOptions)
.then((response) => { response.text(); })
.then((result) => console.log(result))
.catch((error) => {
console.log('error', error);
console.log('trying again');
setLoading(true);
setTimeout(() => { saveParticipateInfo(accountAddress, formState, grc, erc, hash); }, 2000);
setTimeout(() => { saveParticipateInfo(accountAddress, formState, grc, erc, blockHash); }, 2000);
});
setLoading(false);
};

const signedTx = async () => {
// console.log('inside function');
// console.log(grc);
// console.log(document.getElementById('erc') ? document.getElementById('erc').value : new URL(window.location.href).searchParams.get('ref'));
// console.log('inside function');

// console.log('--------------------------');
// console.log(document.getElementById('grc') ? document.getElementById('grc').checkValidity() : 'no grc');
// console.log(document.getElementById('erc') ? document.getElementById('erc').checkValidity() : 'no erc');
// console.log('--------------------------');

if (document.getElementById('grc')) {
if (!document.getElementById('grc').checkValidity()) {
// console.log('return');
return;
}
}
if (document.getElementById('erc')) {
if (!document.getElementById('erc').checkValidity()) {
// console.log('return');
return;
}
}
// console.log('no return');
setStatus('Sending...');
setLoading(true);

const fromAcct = await getFromAcct();
const transformed = transformParams(paramFields, inputParams);
// transformed can be empty parameters

const txExecute = transformed
? api.tx[palletRpc][callable](...transformed)
: api.tx[palletRpc][callable]();

const unsub = await txExecute
.signAndSend(fromAcct, txResHandler)
.catch(txErrHandler);
txxHash = txExecute.hash.toHex();
setUnsub(() => unsub);
};

Expand Down

0 comments on commit c269fdd

Please sign in to comment.