Skip to content

Commit

Permalink
feat: add support for nano contract details (#253)
Browse files Browse the repository at this point in the history
  • Loading branch information
pedroferreira1 authored Apr 12, 2024
1 parent 151ab89 commit e991544
Show file tree
Hide file tree
Showing 8 changed files with 522 additions and 45 deletions.
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 8 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.17.0",
"private": true,
"dependencies": {
"@hathor/wallet-lib": "1.1.0",
"@hathor/wallet-lib": "1.4.0",
"axios": "0.17.1",
"bootstrap": "4.6.2",
"d3-selection": "1.4.2",
Expand Down Expand Up @@ -34,23 +34,17 @@
"scripts": {
"build-css": "sass src:src",
"watch-css": "npm run build-css && sass --watch src:src",
"start-js": "react-scripts start",
"start-js": "react-scripts --openssl-legacy-provider start",
"start": "npm-run-all -p watch-css start-js",
"build-js": "react-scripts --openssl-legacy-provider build",
"build": "npm-run-all build-css build-js",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
}
10 changes: 9 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import Dag from './screens/Dag';
import Dashboard from './screens/Dashboard';
import VersionError from './screens/VersionError';
import WebSocketHandler from './WebSocketHandler';
import NanoContractDetail from './screens/nano/NanoContractDetail';
import BlueprintDetail from './screens/nano/BlueprintDetail';
import { apiLoadErrorUpdate, dashboardUpdate, isVersionAllowedUpdate } from "./actions/index";
import { connect } from "react-redux";
import versionApi from './api/version';
Expand Down Expand Up @@ -59,7 +61,11 @@ class Root extends React.Component {
this.props.apiLoadErrorUpdate({apiLoadError: false});

versionApi.getVersion().then((data) => {
hathorLibConfig.setNetwork(data.network.split('-')[0]);
let network = data.network;
if (data.network.includes('testnet')) {
network = 'testnet';
}
hathorLibConfig.setNetwork(network);
this.props.isVersionAllowedUpdate({allowed: helpers.isVersionAllowed(data.version)});
}, (e) => {
// Error in request
Expand Down Expand Up @@ -117,6 +123,8 @@ class Root extends React.Component {
<NavigationRoute exact path="/statistics" component={Dashboard} />
<NavigationRoute exact path="/token_detail/:tokenUID" component={TokenDetail} />
<NavigationRoute exact path="/address/:address" component={AddressDetail} />
<NavigationRoute exact path="/nano_contract/detail/:nc_id" component={NanoContractDetail} />
<NavigationRoute exact path="/blueprint/detail/:blueprint_id" component={BlueprintDetail} />
<NavigationRoute exact path="" component={DashboardTx} />
</Switch>
</Router>
Expand Down
63 changes: 63 additions & 0 deletions src/api/nanoApi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* 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 = {
/**
* Get the state of a nano contract
*
* @param {string} id Nano contract id
* @param {string[]} fields List of fields to get the state
* @param {string[]} balances List of token uids to get the balance
* @param {string[]} fields List of private methods with parameters to call
*
* For more details, see full node api docs
*/
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);
});
},

/**
* Get the history of transactions of a nano contract
*
* @param {string} id Nano contract id
*
* For more details, see full node api docs
*/
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);
});
},

/**
* Get the blueprint information
*
* @param {string} blueprintId ID of the blueprint
*
* For more details, see full node api docs
*/
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;
112 changes: 86 additions & 26 deletions src/components/tx/TxData.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,20 +67,43 @@ class TxData extends React.Component {
label: 'Funds neighbors',
...this.baseItemGraph
}
]
],
ncDeserializer: null,
ncLoading: false,
};

// 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 () => {
this.calculateTokens();
await this.handleNanoContractFetch();
}

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

this.setState({ ncLoading: true });

const network = hathorLib.config.getNetwork();
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(network);
await deserializer.parseArguments();
this.setState({ ncDeserializer: deserializer, ncLoading: false });
}

/**
Expand Down Expand Up @@ -255,7 +278,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 @@ -275,15 +298,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 @@ -297,7 +320,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 @@ -335,8 +358,6 @@ class TxData extends React.Component {
case 'P2PKH':
case 'MultiSig':
return renderP2PKHorMultiSig(output.decoded);
case 'NanoContractMatchValues':
return renderNanoContractMatchValues(output.decoded);
default:
let script = output.script;
// Try to parse as script data
Expand Down Expand Up @@ -379,11 +400,6 @@ class TxData extends React.Component {
return ret;
}

const renderNanoContractMatchValues = (decoded) => {
const ret = `Match values (nano contract), oracle id: ${decoded.oracle_data_id} hash: ${decoded.oracle_pubkey_hash}`;
return ret;
}

const renderListWithLinks = (hashes, textDark) => {
if (hashes.length === 0) {
return;
Expand Down Expand Up @@ -595,6 +611,47 @@ class TxData extends React.Component {
);
}

const renderNCData = () => {
if (this.state.ncLoading) {
return (
<div className="d-flex flex-column align-items-start common-div bordered-wrapper">
<div>
<label>Loading nano contract data...</label>
</div>
</div>
);
}
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 Down Expand Up @@ -654,22 +711,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 All @@ -692,7 +752,7 @@ class TxData extends React.Component {
</div>
</div>
{ this.state.graphs.map((graph, index) => renderGraph(index)) }
{ hathorLib.helpers.isBlock(this.props.transaction) && renderFeatureActivation() }
{ hathorLib.transactionUtils.isBlock(this.props.transaction) && renderFeatureActivation() }
<div className="d-flex flex-column flex-lg-row align-items-start mb-3 common-div bordered-wrapper w-100">
{this.props.showRaw ? showRawWrapper() : null}
</div>
Expand Down
4 changes: 4 additions & 0 deletions src/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -625,4 +625,8 @@ th.sortable {

.screen-status {
padding-bottom: 20px;
}

.table-method-arguments tbody tr {
background-color: transparent !important;
}
Loading

0 comments on commit e991544

Please sign in to comment.