Skip to content

Commit

Permalink
feat: add support for nano contract details
Browse files Browse the repository at this point in the history
  • Loading branch information
pedroferreira1 committed Jan 23, 2024
1 parent 091259c commit 754acf5
Show file tree
Hide file tree
Showing 3 changed files with 236 additions and 19 deletions.
39 changes: 39 additions & 0 deletions src/api/nanoApi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Copyright (c) Hathor Labs and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import requestExplorerServiceV1 from './axiosInstance';

const nanoApi = {
getState(id, fields, balances, calls) {
const data = { id, fields, balances, calls };
return requestExplorerServiceV1.get(`node_api/nc_state`, {params: data}).then((res) => {
return res.data
}, (res) => {
throw new Error(res.data.message);
});
},

getHistory(id) {
const data = { id };
return requestExplorerServiceV1.get(`node_api/nc_history`, {params: data}).then((res) => {
return res.data
}, (res) => {
throw new Error(res.data.message);
});
},

getBlueprintInformation(blueprintId) {
const data = { blueprint_id: blueprintId };
return requestExplorerServiceV1.get(`node_api/nc_blueprint_information`, {params: data}).then((res) => {
return res.data
}, (res) => {
throw new Error(res.data.message);
});
},
};

export default nanoApi;
91 changes: 72 additions & 19 deletions src/components/tx/TxData.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,20 +62,38 @@ class TxData extends React.Component {
label: 'Funds neighbors',
...this.baseItemGraph
}
]
],
ncDeserializer: null,
};

// Array of token uid that was already found to show the symbol
tokensFound = [];

componentDidMount = () => {
this.calculateTokens();
componentDidMount = async () => {
await this.handleDataFetch();
}

componentDidUpdate = (prevProps) => {
componentDidUpdate = async (prevProps) => {
if (prevProps.transaction !== this.props.transaction) {
this.calculateTokens();
await this.handleDataFetch();
}
}

handleDataFetch = async () => {
await this.handleNanoContractFetch();
this.calculateTokens();
}

handleNanoContractFetch = async () => {
if (this.props.transaction.version !== hathorLib.constants.NANO_CONTRACTS_VERSION) {
return;
}

const ncData = this.props.transaction;
const deserializer = new hathorLib.NanoContractTransactionParser(ncData.nc_blueprint_id, ncData.nc_method, ncData.nc_pubkey, ncData.nc_args);
deserializer.parseAddress();
await deserializer.parseArguments();
this.setState({ ncDeserializer: deserializer });
}

/**
Expand Down Expand Up @@ -235,7 +253,7 @@ class TxData extends React.Component {

render() {
const renderBlockOrTransaction = () => {
if (hathorLib.helpers.isBlock(this.props.transaction)) {
if (hathorLib.transactionUtils.isBlock(this.props.transaction)) {
return 'block';
} else {
return 'transaction';
Expand All @@ -255,15 +273,15 @@ class TxData extends React.Component {

const renderOutputToken = (output) => {
return (
<strong>{this.getOutputToken(hathorLib.wallet.getTokenIndex(output.token_data))}</strong>
<strong>{this.getOutputToken(hathorLib.tokensUtils.getTokenIndexFromData(output.token_data))}</strong>
);
}

const outputValue = (output) => {
if (hathorLib.wallet.isAuthorityOutput(output)) {
if (hathorLib.wallet.isMintOutput(output)) {
if (hathorLib.transactionUtils.isAuthorityOutput(output)) {
if (hathorLib.transactionUtils.isMint(output)) {
return 'Mint authority';
} else if (hathorLib.wallet.isMeltOutput(output)) {
} else if (hathorLib.transactionUtils.isMelt(output)) {
return 'Melt authority';
} else {
// Should never come here
Expand All @@ -277,7 +295,7 @@ class TxData extends React.Component {
}

// if it's an NFT token we should show integer value
const uid = this.getUIDFromTokenData(hathorLib.wallet.getTokenIndex(output.token_data));
const uid = this.getUIDFromTokenData(hathorLib.tokensUtils.getTokenIndexFromData(output.token_data));
const tokenData = this.state.tokens.find((token) => token.uid === uid);
const isNFT = tokenData && tokenData.meta && tokenData.meta.nft;
return helpers.renderValue(output.value, isNFT);
Expand Down Expand Up @@ -463,7 +481,7 @@ class TxData extends React.Component {

const renderGraph = (graphIndex) => {
return (
<div className="d-flex flex-column flex-lg-row align-items-start mb-3 common-div bordered-wrapper w-100">
<div key={graphIndex} className="d-flex flex-column flex-lg-row align-items-start mb-3 common-div bordered-wrapper w-100">
<div className="mt-3 graph-div" key={`graph-${this.state.graphs[graphIndex].name}-${this.props.transaction.hash}`}>
<label className="graph-label">{this.state.graphs[graphIndex].label}:</label>
<a href="true" className="ml-1" onClick={(e) => this.toggleGraph(e, graphIndex)}>{this.state.graphs[graphIndex].showNeighbors ? 'Click to hide' : 'Click to show'}</a>
Expand Down Expand Up @@ -559,6 +577,38 @@ class TxData extends React.Component {
);
}

const renderNCData = () => {
const deserializer = this.state.ncDeserializer;
if (!deserializer) {
// This should never happen
return null;
}
return (
<div className="d-flex flex-column align-items-start common-div bordered-wrapper mr-3">
<div><label>Nano Contract ID:</label> <Link to={`/nano_contract/detail/${this.props.transaction.nc_id}`}> {this.props.transaction.nc_id}</Link></div>
<div><label>Address used to sign:</label> {deserializer.address.base58}</div>
<div><label>Method:</label> {this.props.transaction.nc_method}</div>
<div><label>Arguments:</label> {renderNCArguments(deserializer.parsedArgs)}</div>
</div>
);
}

const renderNCArguments = (args) => {
return args.map((arg) => (
<div key={arg.name}>
<label>{arg.name}:</label> {renderArgValue(arg)}
</div>
));
}

const renderArgValue = (arg) => {
if (arg.type === 'bytes') {
return arg.parsed.toString('hex');
}

return arg.parsed;
}

const isNFTCreation = () => {
if (this.props.transaction.version !== hathorLib.constants.CREATE_TOKEN_TX_VERSION) {
return false;
Expand All @@ -574,22 +624,25 @@ class TxData extends React.Component {
<div className="tx-data-wrapper">
<TxAlerts tokens={this.state.tokens}/>
{this.props.showConflicts ? renderConflicts() : ''}
<div><label>{hathorLib.helpers.isBlock(this.props.transaction) ? 'Block' : 'Transaction'} ID:</label> {this.props.transaction.hash}</div>
<div><label>{hathorLib.transactionUtils.isBlock(this.props.transaction) ? 'Block' : 'Transaction'} ID:</label> {this.props.transaction.hash}</div>
<div className="d-flex flex-column flex-lg-row align-items-start mt-3 mb-3">
<div className="d-flex flex-column align-items-start common-div bordered-wrapper mr-lg-3 w-100">
<div><label>Type:</label> {hathorLib.helpers.getTxType(this.props.transaction)} {isNFTCreation() && '(NFT)'} <TxMarkers tx={this.props.transaction} /></div>
<div><label>Type:</label> {hathorLib.transactionUtils.getTxType(this.props.transaction)} {isNFTCreation() && '(NFT)'} <TxMarkers tx={this.props.transaction} /></div>
<div><label>Time:</label> {dateFormatter.parseTimestamp(this.props.transaction.timestamp)}</div>
<div><label>Nonce:</label> {this.props.transaction.nonce}</div>
<div><label>Weight:</label> {helpers.roundFloat(this.props.transaction.weight)}</div>
{!hathorLib.helpers.isBlock(this.props.transaction) && renderFirstBlockDiv()}
{!hathorLib.transactionUtils.isBlock(this.props.transaction) && renderFirstBlockDiv()}
</div>
<div className="d-flex flex-column align-items-center important-div bordered-wrapper mt-3 mt-lg-0 w-100">
{hathorLib.helpers.isBlock(this.props.transaction) && renderHeight()}
{hathorLib.helpers.isBlock(this.props.transaction) && renderScore()}
{!hathorLib.helpers.isBlock(this.props.transaction) && renderAccWeightDiv()}
{!hathorLib.helpers.isBlock(this.props.transaction) && renderConfirmationLevel()}
{hathorLib.transactionUtils.isBlock(this.props.transaction) && renderHeight()}
{hathorLib.transactionUtils.isBlock(this.props.transaction) && renderScore()}
{!hathorLib.transactionUtils.isBlock(this.props.transaction) && renderAccWeightDiv()}
{!hathorLib.transactionUtils.isBlock(this.props.transaction) && renderConfirmationLevel()}
</div>
</div>
<div className="d-flex flex-row align-items-start mb-3">
{this.props.transaction.version === hathorLib.constants.NANO_CONTRACTS_VERSION && renderNCData()}
</div>
<div className="d-flex flex-column flex-lg-row align-items-start mb-3 w-100">
<div className="f-flex flex-column align-items-start common-div bordered-wrapper mr-lg-3 w-100">
<div><label>Inputs ({ this.props.transaction.inputs.length })</label></div>
Expand Down
125 changes: 125 additions & 0 deletions src/screens/NanoContractDetail.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/**
* Copyright (c) Hathor Labs and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import React from 'react';
import Loading from '../components/Loading';
import TxRow from '../components/tx/TxRow';
import hathorLib from '@hathor/wallet-lib';
import nanoApi from '../api/nanoApi';
import txApi from '../api/txApi';


/**
* Details of a Nano Contract
*
* @memberof Screens
*/
class NanoContractDetail extends React.Component {

state = {
loadingDetail: true,
loadingHistory: true,
ncState: null,
history: null,
errorMessage: null,
}

componentDidMount = () => {
this.loadBlueprintInformation();
this.loadNCHistory();
}

loadBlueprintInformation = async () => {
this.setState({ loadingDetail: true, data: null });
try {
const transactionData = await txApi.getTransaction(this.props.match.params.nc_id);
if (transactionData.tx.version !== hathorLib.constants.NANO_CONTRACTS_VERSION) {
this.setState({ errorMessage: 'Transaction is not a nano contract.' });
}

const blueprintInformation = await nanoApi.getBlueprintInformation(transactionData.tx.nc_blueprint_id);
const ncState = await nanoApi.getState(this.props.match.params.nc_id, Object.keys(blueprintInformation.attributes), [], []);
this.setState({ loadingDetail: false, ncState });
} catch (e) {
this.setState({ loadingDetail: false, errorMessage: 'Error getting nano contract state.' });
}
}

loadNCHistory = () => {
this.setState({ loadingHistory: true, history: null });
nanoApi.getHistory(this.props.match.params.nc_id).then((data) => {
this.setState({ loadingHistory: false, history: data.history });
}, (e) => {
// Error in request
this.setState({ loadingHistory: false, errorMessage: 'Error getting nano contract history.' });
});
}

render() {
if (this.state.errorMessage) {
return <p className='text-danger mb-4'>{this.state.errorMessage}</p>;
}

if (this.state.loadingHistory || this.state.loadingDetail) {
return <Loading />;
}

const loadTable = () => {
return (
<div className="table-responsive mt-5">
<table className="table table-striped" id="tx-table">
<thead>
<tr>
<th className="d-none d-lg-table-cell">Hash</th>
<th className="d-none d-lg-table-cell">Timestamp</th>
<th className="d-table-cell d-lg-none" colSpan="2">Hash<br/>Timestamp</th>
</tr>
</thead>
<tbody>
{loadTableBody()}
</tbody>
</table>
</div>
);
}

const loadTableBody = () => {
return this.state.history.map((tx, idx) => {
// For some reason this API returns tx.hash instead of tx.tx_id like the others
tx.tx_id = tx.hash;
return (
<TxRow key={tx.tx_id} tx={tx} />
);
});
}

const renderNCAttributes = () => {
return Object.keys(this.state.ncState.fields).map((field) => (
<p><strong>{field}: </strong>{this.state.ncState.fields[field].value}</p>
));
}

// TODO
//const isTokenNFT = isNFT(this.state.data.nc_data.token);
return (
<div className="content-wrapper">
<h3 className="mt-4">Nano Contract Detail</h3>
<div className="mt-5">
<p><strong>Nano Contract ID: </strong>{this.props.match.params.nc_id}</p>
<p><strong>Blueprint: </strong>{this.state.ncState.blueprint_name}</p>
<h4 className="mt-5 mb-4">Attributes</h4>
{ renderNCAttributes() }
<hr />
<h3 className="mt-4">History</h3>
{this.state.history && loadTable()}
</div>
</div>
);
}
}

export default NanoContractDetail;

0 comments on commit 754acf5

Please sign in to comment.