Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New tab for show internal transactions history for a given address #346

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
327 changes: 327 additions & 0 deletions src/components/InternalTransactionTable.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,327 @@
<script>
import BlockField from 'components/BlockField';
import DateField from 'components/DateField';
import TransactionField from 'components/TransactionField';
import MethodField from 'components/MethodField';
import { formatWei } from 'src/lib/utils';
import { TRANSFER_SIGNATURES } from 'src/lib/abi/signature/transfer_signatures';
import InternalTxns from 'components/Transaction/InternalTxns';

const PAGE_KEY = 'page';
const PSIZE_KEY = 'pagesize';

export default {
name: 'InternalTransactionTable',
components: {
TransactionField,
DateField,
BlockField,
MethodField,
InternalTxns,
},
props: {
title: {
type: String,
required: true,
},
filter: {
type: Object,
default: () => ({}),
},
initialPageSize: {
type: Number,
default: 1,
},
},
data() {
const columns = [
{
name: 'hash',
label: '',
align: 'left',
},
{
name: 'block',
label: '',
align: 'left',
},
{
name: 'date',
label: '',
align: 'left',
},
{
name: 'method',
label: '',
align: 'left',
},
{
name: 'int_txns',
label: '',
align: 'right',
},
];

return {
rows: [],
columns,
transactions: [],
pageSize: this.initialPageSize,
total: null,
loading: false,
pagination: {
sortBy: 'date',
descending: true,
page: 1,
rowsPerPage: 10,
rowsNumber: 0,
},
showDateAge: true,
};
},
async created() {
// initialization of the translated texts
this.columns[0].label = this.$t('components.tx_hash');
this.columns[1].label = this.$t('components.block');
this.columns[2].label = this.$t('components.date');
this.columns[3].label = this.$t('components.method');
this.columns[4].label = this.$t('components.internal_txns');
},
mounted() {
addEventListener('popstate', this.popstate.bind(this));
// restoring a possible last pagination state from the URL
const params = new URLSearchParams(window.location.search);
let page = params.get(PAGE_KEY) || window.history.state?.pagination?.page;
let size = params.get(PSIZE_KEY) || window.history.state?.pagination?.size;
this.setPagination(page, size);
},
beforeUnmount() {
window.removeEventListener('popstate', this.popstate);
},
methods: {
popstate(event) {
const page = event.state.pagination.page;
const size = event.state.pagination.size;
this.setPagination(page, size);
},
setPagination(page, size) {
if (page) {
this.pagination.page = Number(page);
}
if (size) {
this.pagination.rowsPerPage = Number(size);
}
this.onRequest({
pagination: this.pagination,
});
},
async onRequest(props) {
this.loading = true;

// saving last pagination state
const url = new URL(window.location);
url.searchParams.set(PAGE_KEY, props.pagination.page);
url.searchParams.set(PSIZE_KEY, props.pagination.rowsPerPage);
const pagination = {
page: props.pagination.page,
size: props.pagination.rowsPerPage,
};
const params = new URLSearchParams(window.location.search);
const page_url = params.get(PAGE_KEY);
const size_url = params.get(PSIZE_KEY);
// this is a workaround to avoid the pushState() to fail
// https://github.com/vuejs/router/issues/366#issuecomment-1408501848
const current = '/';
if (this.pagination.page !== props.pagination.page ||
this.pagination.rowsPerPage !== props.pagination.rowsPerPage
) {
window.history.pushState({ pagination, current }, '', url.toString());
} else if (!page_url || !size_url) {
window.history.replaceState({ pagination, current }, '', url.toString());
}

// this line cleans the table for a second and the components have to be created again (clean)
this.rows = [];

const { page, rowsPerPage, sortBy, descending } = props.pagination;
let result = await this.$evmEndpoint.get(this.getPath(props));

if (this.total === null) {
this.pagination.rowsNumber = result.data.total.value;
}

this.pagination.page = page;
this.pagination.rowsPerPage = rowsPerPage;
this.pagination.sortBy = sortBy;
this.pagination.descending = descending;

this.transactions = [...result.data.transactions];
this.rows = this.transactions;

for (const transaction of this.transactions) {
try {
transaction.transfer = false;
transaction.value = formatWei(transaction.value.toLocaleString(0, { useGrouping: false }), 18);
if (transaction.input_data === '0x') {
continue;
}
if(!transaction.to) {
continue;
}

const contract = await this.$contractManager.getContract(
transaction.to,
);

if (!contract) {
continue;
}

const parsedTransaction = await contract.parseTransaction(
transaction.input_data,
);
if (parsedTransaction) {
transaction.parsedTransaction = parsedTransaction;
transaction.contract = contract;
}
// Get ERC20 transfer from main function call
let signature = transaction.input_data.substring(0, 10);
if (
signature &&
TRANSFER_SIGNATURES.includes(signature) &&
transaction.parsedTransaction.args['amount']
) {
let token = await this.$contractManager.getTokenData(transaction.to, 'erc20');
if(transaction.contract && token && token.decimals){
transaction.transfer = {
'value': `${formatWei(transaction.parsedTransaction.args['amount'], token.decimals)}`,
'symbol': token.symbol,
};
}
}
} catch (e) {
console.error(
`Failed to parse data for transaction, error was: ${e.message}`,
);
// notifiy user
this.$q.notify({
message: this.$t('components.failed_to_parse_transaction', { message: e.message }),
color: 'negative',
position: 'top',
timeout: 5000,
});
}
}
this.rows = this.transactions;
this.loading = false;
},
getPath(props) {
const { page, rowsPerPage, descending } = props.pagination;
let path = `/v2/evm/get_transactions?limit=${
rowsPerPage === 0 ? 500 : rowsPerPage
}`;
const filter = Object.assign({}, this.filter ? this.filter : {});
if (filter.address) {
path += `&address=${filter.address}`;
}

if (filter.block) {
path += `&block=${filter.block}`;
}

if (filter.hash) {
path += `&hash=${filter.hash}`;
}

path += `&skip=${(page - 1) * rowsPerPage}`;
path += `&sort=${descending ? 'desc' : 'asc'}`;

return path;
},
toggleDateFormat() {
this.showDateAge = !this.showDateAge;
},
},
};
</script>

<template>
<q-table
v-model:pagination="pagination"
:rows="rows"
:row-key="row => row.hash"
:columns="columns"
:loading="loading"
:rows-per-page-options="[10, 20, 50]"
flat
@request="onRequest"
>
<template v-slot:header="props">
<q-tr :props="props">
<q-th v-for="col in props.cols" :key="col.name" :props="props">
<div :class="[ 'u-flex--center-y', { 'u-flex--right': col.align === 'right' } ]" >
{{ col.label }}
<template v-if="col.name === 'date'">
<q-icon
class="info-icon"
name="fas fa-info-circle"
@click="toggleDateFormat"
>
<q-tooltip anchor="bottom middle" self="bottom middle" :offset="[0, 36]">
{{ $t('components.click_to_change_format') }}
</q-tooltip>
</q-icon>
</template>
<template v-if="col.name === 'method'">
<q-icon class="info-icon" name="fas fa-info-circle" />
<q-tooltip anchor="bottom middle" self="top middle" max-width="10rem">
{{ $t('components.executed_based_on_decoded_data') }}
</q-tooltip>
</template>
</div>
</q-th>
<q-td auto-width/>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td key="hash" :props="props">
<TransactionField :transaction-hash="props.row.hash"/>
</q-td>
<q-td key="block" :props="props">
<BlockField :block="props.row.block"/>
</q-td>
<q-td key="date" :props="props">
<DateField :epoch="props.row.epoch" :force-show-age="showDateAge"/>
</q-td>
<q-td key="method" :props="props">
<MethodField v-if="props.row.parsedTransaction" :trx="props.row" :shortenName="true"/>
</q-td>
<q-td key="int_txns" :props="props">
<span v-if="props.row.itxs.length > 0">
<b> {{ $t('components.n_internal_txns', {amount: props.row.itxs.length} ) }} </b>
</span>
<span v-else>{{ $t('components.none') }}</span>
</q-td>
<q-td auto-width>
<q-icon
v-if="props.row.itxs.length > 0"
:name="props.expand ? 'expand_more' : 'expand_less'"
size="sm"
@click="props.expand = !props.expand"
/>
</q-td>
</q-tr>
<q-tr
v-show="!props.expand"
v-if="props.row.itxs.length > 0"
:props="props"
class="q-virtual-scroll--with-prev"
>
<q-td colspan="100%">
<InternalTxns :itxs="props.row.itxs"/>
</q-td>
</q-tr>
</template>
</q-table>
</template>
3 changes: 3 additions & 0 deletions src/i18n/en-us/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ export default {
value: 'Value',
},
components: {
internal_txns: 'Internal Transactions',
n_internal_txns: '{ amount } internal transactions',
none: 'None',
verify_prompt: 'This contract has not been verified. Would you like to upload the contract(s) and metadata to verify source now?',
verify_contract: 'Verify Contract',
search_evm_address_failed: 'Search for EVM address linked to { accountName } native account failed. You can create one at wallet.telos.net',
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/es-es/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ export default {
value: 'Valor',
},
components: {
internal_txns: 'Transacciones internas',
n_internal_txns: '{ amount } transacciones internas',
none: 'Ninguno',
verify_prompt: 'Este contrato no ha sido verificado. ¿Le gustaría cargar el (los) contrato (s) y los metadatos para verificar el código fuente ahora?',
verify_contract: 'Verificar contrato',
search_evm_address_failed: 'La búsqueda de la dirección EVM vinculada a la cuenta nativa { accountName } falló. Puede crear una en wallet.telos.net',
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/pt-br/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ export default {
value: 'Valor',
},
components: {
internal_txns: 'Transações internas',
n_internal_txns: '{ amount } transações internas',
none: 'Nenhum',
verify_prompt: 'Este contrato ainda não foi verificado. Você gostaria de carregar o(s) contrato(s) e os metadados para verificar o código-fonte agora?',
verify_contract: 'Verificar contrato',
search_evm_address_failed: 'A busca pelo endereço EVM vinculado à conta nativa { accountName } falhou. Você pode criar uma em wallet.telos.net',
Expand Down
Loading