diff --git a/README.md b/README.md index 57a0f713..bafa55ca 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ This includes following key features: - BP Voting and Governance - Premium Name bids -Following, excelent products can be taken as reference points for further features: +Following, excellent products can be taken as reference points for further features: - [EOS Authority](https://eosauthority.com/) - [Blocks](https://bloks.io/) @@ -112,7 +112,7 @@ app running at localhost:4000 [Vue 2 documentation](https://vuejs.org/v2/api/) -[Vue 3 documenation](https://v3.vuejs.org/) +[Vue 3 documentation](https://v3.vuejs.org/) [Testing Quasar with Jest](https://github.com/quasarframework/quasar-testing/tree/next/packages/unit-jest) diff --git a/src/boot/wharf.ts b/src/boot/wharf.ts new file mode 100644 index 00000000..a70e613d --- /dev/null +++ b/src/boot/wharf.ts @@ -0,0 +1,45 @@ +import { Session, SessionKit } from '@wharfkit/session'; +import { TransactPluginResourceProvider } from '@wharfkit/transact-plugin-resource-provider'; +import { WalletPluginAnchor } from '@wharfkit/wallet-plugin-anchor'; +import { WalletPluginCleos } from '@wharfkit/wallet-plugin-cleos'; +import { WalletPluginPrivateKey } from '@wharfkit/wallet-plugin-privatekey'; +import WebRenderer from '@wharfkit/web-renderer'; +import { boot } from 'quasar/wrappers'; +import { getChain } from 'src/config/ConfigManager'; +import { Chain } from 'src/types/Chain'; + +const chain: Chain = getChain(); + +declare module 'vue' { + interface ComponentCustomProperties { + $kit: SessionKit; + $user: Session; + } +} + +export const ui = new WebRenderer(); + +export const kit = new SessionKit({ + appName: process.env.APP_NAME, + chains: [ + { + id: chain.getChainId(), + url: String(chain.getRPCEndpoint()), + }, + ], + ui, + walletPlugins: [ + new WalletPluginAnchor(), + new WalletPluginCleos(), + new WalletPluginPrivateKey('5Jtoxgny5tT7NiNFp1MLogviuPJ9NniWjnU4wKzaX4t7pL4kJ8s'), + ], +}, +{ + transactPlugins: [ + new TransactPluginResourceProvider(), + ], +}); + +export default boot(({ app }) => { + app.config.globalProperties.$kit = kit; +}); diff --git a/src/components/BlockCard.vue b/src/components/BlockCard.vue index efbeab91..406298b7 100644 --- a/src/components/BlockCard.vue +++ b/src/components/BlockCard.vue @@ -15,6 +15,10 @@ import { formatDate } from 'src/utils/string-utils'; export default defineComponent({ name: 'BlockCard', props: { + block_num: { + type: String, + required: true, + }, block: { type: Object as PropType, required: false, @@ -22,8 +26,10 @@ export default defineComponent({ }, }, setup(props) { + const loading = ref(true); const router = useRouter(); const q = useQuasar(); + const blockNum = computed(() => props.block_num); const Block = computed(() => props.block); const blockInfo = ref<{ key: string; value: string }[]>([]); async function nextBlock() { @@ -96,6 +102,7 @@ export default defineComponent({ }, { key: 'Actions', value: actionCount.toString() }, ]; + loading.value = false; } } watch(Block, () => { @@ -105,13 +112,14 @@ export default defineComponent({ setBlockData(); }); return { - block_num: computed(() => Block.value?.block_num || 0), nextBlock, previousBlock, numberWithCommas, formatDate, copy, blockInfo, + blockNum, + loading, }; }, }); @@ -154,7 +162,7 @@ export default defineComponent({
-
{{numberWithCommas(block_num)}}
+
{{numberWithCommas(parseInt(block_num))}}
- -
SUMMARY
+ + +
+ +
-
- +
diff --git a/src/components/SendDialog.vue b/src/components/SendDialog.vue index 72a85a55..5f15d51f 100644 --- a/src/components/SendDialog.vue +++ b/src/components/SendDialog.vue @@ -81,11 +81,12 @@ export default defineComponent({ }; const resetForm = () => { + const token = networksStore.getCurrentNetwork.getSystemToken(); sendToken.value = { - symbol: networksStore.getCurrentNetwork.getSystemToken().symbol, - precision: 4, - amount: 0, - contract: 'eosio.token', + symbol: token.symbol, + precision: token.precision, + amount: token.amount, + contract: token.contract, }; }; diff --git a/src/components/TransactionsTable.vue b/src/components/TransactionsTable.vue index fd5fffe3..4d36c478 100644 --- a/src/components/TransactionsTable.vue +++ b/src/components/TransactionsTable.vue @@ -252,9 +252,8 @@ export default defineComponent({ const filterRows = () => { filteredRows.value = rows.value; }; - const loadTableData = async (): Promise => { - let tableData: Action[]; + let tableData: Action[] = []; if (isTransaction.value) { tableData = (await api.getTransaction(account.value)).actions; } else if (hasActions.value) { @@ -262,46 +261,21 @@ export default defineComponent({ } else { const page = paginationSettings.value.page; let limit = paginationSettings.value.rowsPerPage; - - let notified = accountsModel.value ?? ''; - let after = ''; - let before = ''; - if (toDateModel.value !== now) { - before = new Date(toDateModel.value).toISOString(); - } - if (fromDateModel.value !== '') { - after = new Date(fromDateModel.value).toISOString(); - } + const notified = accountsModel.value ?? ''; + const after = fromDateModel.value !== '' ? new Date(fromDateModel.value).toISOString() : ''; + const before = toDateModel.value !== now ? new Date(toDateModel.value).toISOString() : ''; const sort = paginationSettings.value.descending ? 'desc' : 'asc'; + const extras: {[key:string]:string} = {}; - let extras: {[key:string]:string} | null = tokenModel.value ? { 'act.account': tokenModel.value.contract } : null; - if (actionsModel.value) { - extras = extras ? { - ...extras, - 'act.name': actionsModel.value, - } : { - 'act.name': actionsModel.value, - }; - } - - if (page > 1 && currentFirstAction.value === 0) { - currentFirstAction.value = rows.value[0]?.action.global_sequence; + if (tokenModel.value) { + extras['act.account'] = tokenModel.value.contract; + // Increase limit to allow for filtering + limit = limit * 3; } - - if (currentFirstAction.value > 0) { - extras = extras ? { - ...extras, - 'global_sequence': '0-' + currentFirstAction.value.toString(), - } : { - 'global_sequence': '0-' + currentFirstAction.value.toString(), - }; + if (actionsModel.value) { + extras['act.name'] = actionsModel.value; } - // if token is selected, we need to get all transactions and filter them - // so we eventually will need more than the current page size - if (tokenModel.value) { - limit = 100; - } const response = await api.getTransactions({ page, limit, @@ -319,36 +293,42 @@ export default defineComponent({ if (tableData) { if (tokenModel.value) { tableData = tableData.filter( - item => (item.act.data as {quantity?:string}).quantity?.includes(tokenModel.value.symbol), + item => (item.act.data as {quantity?:string})?.quantity?.includes(tokenModel.value.symbol), ); - - // take only the first aginationSettings.value.rowsPerPage items - tableData = tableData.slice(0, paginationSettings.value.rowsPerPage); } - rows.value = tableData.map(item => ({ - name: item.trx_id, - transaction: { id: item.trx_id, type: 'transaction' }, - timestamp: item['@timestamp'] || item.timestamp, - action: item, - data: hasActions.value - ? { data: item.data, name: item.account } - : { data: item.act.data, name: item.act.name }, - actions: [ - { - name: item.trx_id, - transaction: { id: item.trx_id, type: 'transaction' }, - timestamp: item['@timestamp'], - action: item, - data: hasActions.value - ? { - data: item.data, - name: item.account, - } - : { data: item.act.data as unknown, name: item.act.name }, - }, - ], - })); + const flattenedData: TransactionTableRow[] = []; + tableData.forEach((action) => { + const actionsArray = action.act?.data ? [action] : []; + actionsArray.forEach((act) => { + flattenedData.push({ + name: act.trx_id, + transaction: { id: act.trx_id, type: 'transaction' }, + timestamp: act['@timestamp'] || act.timestamp, + action: act, + data: { + data: act.act.data, + name: act.act.name, + }, + actions: actionsArray.map(a => ({ + name: a.trx_id, + transaction: { id: a.trx_id, type: 'transaction' }, + timestamp: a['@timestamp'] || a.timestamp, + action: a, + data: { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + data: a.act.data, + name: a.act.name, + }, + })), + }); + }); + }); + + // Apply pagination after filtering + const startIndex = (paginationSettings.value.page - 1) * paginationSettings.value.rowsPerPage; + rows.value = flattenedData.slice(startIndex, startIndex + paginationSettings.value.rowsPerPage); + totalRows.value = flattenedData.length; } void filterRows(); }; diff --git a/src/config/chains/wax/index.ts b/src/config/chains/wax/index.ts index 8e1b86aa..444c23ec 100644 --- a/src/config/chains/wax/index.ts +++ b/src/config/chains/wax/index.ts @@ -14,7 +14,7 @@ const NAME = 'wax'; const DISPLAY = 'WAX'; const TOKEN = { symbol: 'WAX', - precision: 4, + precision: 8, amount: 0, contract: 'eosio.token', } as Token; diff --git a/src/pages/Block.vue b/src/pages/Block.vue index 32ccd470..221c1328 100644 --- a/src/pages/Block.vue +++ b/src/pages/Block.vue @@ -13,26 +13,51 @@ export default defineComponent({ const router = useRouter(); const found = ref(true); const block = ref(null); + const block_num = ref(route.params.block as string); const actions = ref([]); const tab = ref((route.query['tab'] as string) || 'actions'); + const tries = 3; + const error = ref(''); + // tryToLoadBlock is a function that tries to load a block from the API at least 3 times before giving up + const tryToLoadBlock = async (block_num: string) => { + error.value = ''; + let block = null; + for (let i = 0; i < tries; i++) { + try { + block = await api.getBlock(block_num); + break; + } catch (e) { + console.error(`Failed to load block ${block_num} on try ${i + 1}`); + if (i === tries - 1) { + throw e; + } + } + } + return block; + }; onMounted(async () => { // api get block and set block - block.value = await api.getBlock(route.params.block as string); - block.value.transactions.forEach((tr) => { - const act = tr.trx.transaction?.actions.map(act => ({ - ...act, - trx_id: tr.trx.id, - act: { - ...act.act, - name: act.name, - data: act.data, - account: act.account, - }, - '@timestamp': block.value.timestamp, - })); - actions.value = actions.value.concat(act); - }); - found.value = !!block.value; + try { + block.value = await tryToLoadBlock(block_num.value); + block.value.transactions.forEach((tr) => { + const act = tr.trx.transaction?.actions.map(act => ({ + ...act, + trx_id: tr.trx.id, + act: { + ...act.act, + name: act.name, + data: act.data, + account: act.account, + }, + '@timestamp': block.value.timestamp, + })); + actions.value = actions.value.concat(act); + }); + found.value = !!block.value; + } catch (e) { + found.value = false; + error.value = 'Unable to load block, try refreshing the page'; + } }); watch([tab], () => { void router.push({ @@ -48,6 +73,8 @@ export default defineComponent({ found, Actions: actions, block, + block_num, + error, }; }, components: { @@ -61,7 +88,12 @@ export default defineComponent({
- +
@@ -70,6 +102,9 @@ export default defineComponent({
block not found
+ +
{{ error }}
+
diff --git a/src/pages/ProposalItem.vue b/src/pages/ProposalItem.vue index e41982f1..c70cfca7 100644 --- a/src/pages/ProposalItem.vue +++ b/src/pages/ProposalItem.vue @@ -40,6 +40,8 @@ export default defineComponent({ const multsigTransactionData = ref([]); const requestedApprovalsRows = ref([]); + const activeProducers = ref([]); + const activeProducersApproved = ref([]); const requestedApprovalsColumns = [ { @@ -99,15 +101,7 @@ export default defineComponent({ !isCanceled.value )); - const producersApprovalStatus = computed(() => { - const allProducers = requestedApprovalsRows.value.filter( - item => item.isBp, - ); - const producersHaveAlreadyApproved = allProducers.filter( - item => item.status, - ); - return `${producersHaveAlreadyApproved.length}/${allProducers.length}`; - }); + const producersApprovalStatus = computed(() => `${activeProducersApproved.value.length}/${activeProducers.value.length}`); function handleError(e: unknown, defaultMessage: string) { const error = JSON.parse(JSON.stringify(e)) as Error; @@ -282,7 +276,6 @@ export default defineComponent({ proposer.value = proposal.proposer; const totalRequestedApprovals = proposal.provided_approvals.length + proposal.requested_approvals.length; - isApproved.value = proposal.provided_approvals.length === totalRequestedApprovals; approvalStatus.value = `${proposal.provided_approvals.length}/${totalRequestedApprovals}`; isExecuted.value = proposal.executed; @@ -302,6 +295,22 @@ export default defineComponent({ requestedApprovalsRows.value = requestedApprovalsRowsValue; multsigTransactionData.value = multsigTransactionDataValue; + activeProducers.value = requestedApprovalsRows.value.filter( + item => item.isBp, + ); + activeProducersApproved.value = activeProducers.value.filter( + item => item.status, + ); + + if (activeProducers.value.length === 0) { + // No BPs are involved in the proposal, so we need to 100% of the requested approvals + isApproved.value = proposal.provided_approvals.length === totalRequestedApprovals; + } else { + // If BPs are involved, we need 2/3 (+1) of the BPs to approve the proposal + const approval = (activeProducers.value.length * 2 / 3) + 1; + isApproved.value = activeProducersApproved.value.length >= Math.floor(approval); + } + const transactions = await handleTransactionHistory(proposal.block_num); isCanceled.value = transactions.some( diff --git a/yarn.lock b/yarn.lock index c376a568..a3f3b28a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5703,9 +5703,9 @@ flatted@^3.1.0: integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg== follow-redirects@^1.0.0, follow-redirects@^1.14.0: - version "1.15.4" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf" - integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw== + version "1.15.6" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" + integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== for-in@^1.0.2: version "1.0.2" @@ -6496,9 +6496,9 @@ ip-regex@^4.1.0: integrity sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q== ip@^1.1.5: - version "1.1.8" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.8.tgz#ae05948f6b075435ed3307acce04629da8cdbf48" - integrity sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg== + version "1.1.9" + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.9.tgz#8dfbcc99a754d07f425310b86a99546b1151e396" + integrity sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ== ipaddr.js@1.9.1: version "1.9.1" @@ -11642,9 +11642,9 @@ webpack-chain@6.5.1: javascript-stringify "^2.0.1" webpack-dev-middleware@^5.3.1: - version "5.3.3" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz#efae67c2793908e7311f1d9b06f2a08dcc97e51f" - integrity sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA== + version "5.3.4" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz#eb7b39281cbce10e104eb2b8bf2b63fce49a3517" + integrity sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q== dependencies: colorette "^2.0.10" memfs "^3.4.3"