diff --git a/jest.config.js b/jest.config.js index 406c6fcf..5ed7286d 100755 --- a/jest.config.js +++ b/jest.config.js @@ -4,12 +4,8 @@ const esModules = ['quasar', 'quasar/lang', 'lodash-es'].join('|'); module.exports = { globals: { __DEV__: true, - // Remove if using `const enums` - // See https://huafu.github.io/ts-jest/user/config/isolatedModules#example - 'ts-jest': { - isolatedModules: true, - }, }, + testEnvironment: 'jsdom', // noStackTrace: true, // bail: true, // cache: false, @@ -67,7 +63,7 @@ module.exports = { // See https://github.com/vuejs/vue-jest/issues/188#issuecomment-620750728 moduleFileExtensions: ['vue', 'js', 'jsx', 'json', 'ts', 'tsx'], moduleNameMapper: { - '^quasar$': 'quasar/dist/quasar.esm.prod.js', + '^quasar$': 'quasar/dist/quasar.client.js', '^~/(.*)$': '/$1', '^src/(.*)$': '/src/$1', '^app/(.*)$': '/$1', @@ -77,16 +73,17 @@ module.exports = { '^assets/(.*)$': '/src/assets/$1', '^boot/(.*)$': '/src/boot/$1', '.*css$': '@quasar/quasar-app-extension-testing-unit-jest/stub.css', + '^@vue/test-utils': '/node_modules/@vue/test-utils/dist/vue-test-utils.cjs.js', }, transform: { // See https://jestjs.io/docs/en/configuration.html#transformignorepatterns-array-string [`^(${esModules}).+\\.js$`]: 'babel-jest', - '^.+\\.(ts|js|html)$': 'ts-jest', + '^.+\\.(ts|js|html)$': ['ts-jest', { isolatedModules: true }], // vue-jest uses find-babel-file, which searches by this order: // (async) .babelrc, .babelrc.js, package.json, babel.config.js // (sync) .babelrc, .babelrc.js, babel.config.js, package.json // https://github.com/tleunen/find-babel-config/issues/33 - '.*\\.vue$': 'vue-jest', + '.*\\.vue$': '@vue/vue3-jest', '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', }, diff --git a/package.json b/package.json index 4562cf0b..74805a2b 100644 --- a/package.json +++ b/package.json @@ -14,14 +14,14 @@ }, "dependencies": { "@quasar/extras": "^1.13.4", + "@vueuse/core": "^10.9.0", "@wharfkit/session": "^1.2.8", "@wharfkit/transact-plugin-resource-provider": "^1.1.1", "@wharfkit/wallet-plugin-anchor": "^1.3.4", "@wharfkit/wallet-plugin-cleos": "^1.1.1", "@wharfkit/wallet-plugin-privatekey": "^1.1.0", "@wharfkit/web-renderer": "^1.2.4", - "@vueuse/core": "^10.9.0", - "axios": "^0.21.1", + "axios": "^1.7.2", "core-js": "^3.6.5", "csvtojson": "^2.0.10", "fast-sha256": "^1.3.0", @@ -31,27 +31,32 @@ "ol": "^6.14.1", "pinia": "^2.1.6", "quasar": "^2.6.2", - "vue": "^3.3.0", + "vue": "^3.4.27", "vue-json-viewer": "^3.0.4", "vue-router": "^4.0.0", "vue3-openlayers": "^0.1.63" }, "devDependencies": { "@babel/eslint-parser": "^7.13.14", + "@jest/globals": "^29.7.0", "@pinia/testing": "^0.1.3", "@quasar/app-webpack": "^3.5.3", "@quasar/quasar-app-extension-testing-unit-jest": "^3.0.0-alpha.10", "@types/jest": "^27.4.0", "@types/node": "^12.20.21", - "@typescript-eslint/eslint-plugin": "^4.16.1", - "@typescript-eslint/parser": "^4.16.1", + "@typescript-eslint/eslint-plugin": "^7.13.0", + "@typescript-eslint/parser": "^7.13.0", + "@vue/test-utils": "^2.4.6", + "@vue/vue3-jest": "^29.2.6", "dotenv": "^14.3.0", - "eslint": "^7.14.0", - "eslint-plugin-jest": "^25.2.2", + "eslint": "^8.57.0", + "eslint-plugin-jest": "^27.1.3", "eslint-plugin-unused-imports": "^3.0.0", "eslint-plugin-vue": "^9.0.0", + "jest": "^29.2.2", + "jest-serializer-vue": "^3.1.0", "node-polyfill-webpack-plugin": "^1.1.4", - "vue-property-decorator": "^9.1.2" + "ts-jest": "^29.1.4" }, "browserslist": [ "last 10 Chrome versions", diff --git a/public/chains/wax-testnet/favicon.png b/public/chains/wax-testnet/favicon.png new file mode 100644 index 00000000..9f1d91b2 Binary files /dev/null and b/public/chains/wax-testnet/favicon.png differ diff --git a/public/chains/wax-testnet/logo_lg.png b/public/chains/wax-testnet/logo_lg.png new file mode 100644 index 00000000..9f1d91b2 Binary files /dev/null and b/public/chains/wax-testnet/logo_lg.png differ diff --git a/src/api/antelopeV1.ts b/src/api/antelopeV1.ts new file mode 100644 index 00000000..d2c08b92 --- /dev/null +++ b/src/api/antelopeV1.ts @@ -0,0 +1,183 @@ +/* see https://github.com/greymass/eosio-core/blob/master/test/api.ts for documentation */ +import { + ABI, + ABIDef, + ABISerializable, + Action, + ActionType, + API, + APIClient, + Asset, + Name, + PublicKey, + Serializer, +} from '@wharfkit/session'; +import axios from 'axios'; +import { addInterceptors } from 'src/api/axiosInterceptors'; +import { useNetworksStore } from 'src/stores/networks'; +import { + GetProducers, + GetTableRowsParams, +} from 'src/types'; + +const networksStore = useNetworksStore(); +const eosioAxios = axios.create({ baseURL: networksStore.getCurrentNetwork.getV1Endpoint() }); +addInterceptors(eosioAxios); + +const eosio = new APIClient({ url: networksStore.getCurrentNetwork.getV1Endpoint() }); + +export const getAccount = async function ( + address: string, +): Promise { + try { + return await eosio.v1.chain.get_account(address); + } catch (e) { + console.error('Error on v1/chain/get_account', e); + } +}; + +export const getAccountsByPublicKey = async function ( + key: PublicKey, +): Promise<{ account_names: Name[] }> { + try { + return await eosio.v1.history.get_key_accounts(key); + } catch(e) { + console.error('Error on v1/history/get_key_accounts', e); + } + +}; + +export const getTokenBalances = async function ( + address: string, +): Promise { + const { contract } = networksStore.getCurrentNetwork.getSystemToken(); + try { + return await eosio.v1.chain.get_currency_balance(contract, address); + } catch(e) { + console.error('Error on v1/chain/get_currency_balance', e); + } +}; + +export const getTableRows = async function ( + tableInput: GetTableRowsParams, +): Promise { + try { + return await eosio.v1.chain.get_table_rows(tableInput); + } catch(e) { + console.error('Error on v1/chain/get_table_rows', e); + } +}; + +export const getTableByScope = async function ( + data: API.v1.GetTableByScopeParams, +): Promise { + try { + return await eosio.v1.chain.get_table_by_scope(data); + } catch(e) { + console.error('Error on v1/chain/get_table_by_scope', e); + } +}; + +export const getTransactionV1 = async function ( + id?: string, +): Promise { + try { + return await eosio.v1.history.get_transaction(id); + } catch(e) { + console.error('Error on v1/history/get_transaction', e); + } +}; + +export const getBlock = async function ( + block: string, +):Promise { + try { + return await eosio.v1.chain.get_block(block); + } catch(e) { + console.error('Error on v1/chain/get_block', e); + } +}; + +export const getInfo = async function (): +Promise { + try { + return await eosio.v1.chain.get_info(); + } catch(e) { + console.error('Error on v1/chain/get_info', e); + } +}; + +export const getProducerSchedule = async function (): +Promise { + try { + return await eosio.v1.chain.get_producer_schedule(); + } catch(e) { + console.error('Error on v1/chain/get_producer_schedule', e); + } +}; + +export const getProducers = async function (): +Promise { + try { + const response = await eosioAxios.post('v1/chain/get_producers', { + json: true, + limit: 10000, + }); + return response.data as GetProducers; + } catch(e) { + console.error('Error on v1/chain/get_producers', e); + } +}; + +export const getABI = async function ( + account: string, +): Promise { + try { + return await eosio.v1.chain.get_abi(account); + } catch(e) { + console.error('Error on v1/chain/get_abi', e); + } +}; + + +/** non API */ +export const deserializeActionData = async function ( + data: ActionType, +): Promise { + const contractAccount = data.account.toString(); + const abi = await getABI(contractAccount); + + if (!abi.abi) { + throw new Error(`No ABI for ${String(data.account)}`); + } + + const action = Action.from(data, abi.abi); + return Serializer.objectify(action.decodeData(abi.abi)) as ABISerializable; +}; + +export const deserializeActionDataFromAbi = function ( + data: ActionType, + abi: ABIDef, +): ABISerializable { + if (!abi) { + throw new Error(`No ABI for ${String(data.account)}`); + } + const action = Action.from(data, abi); + // eslint-disable-next-line + return Serializer.objectify(action.decodeData(abi)); +}; + +export const serializeActionData = async function ( + account: string, + name: string, + data: unknown, +): Promise { + const response = await getABI(account); + if (!response) { + throw new Error(`No ABI for ${account}`); + } + + const abi = ABI.from(response.abi); + const { hexString } = Serializer.encode({ object: data, abi, type: name }); + return hexString; +}; diff --git a/src/api/axiosInterceptors.ts b/src/api/axiosInterceptors.ts new file mode 100644 index 00000000..76800a3f --- /dev/null +++ b/src/api/axiosInterceptors.ts @@ -0,0 +1,38 @@ +import { + AxiosError, + AxiosInstance, + AxiosResponse, + InternalAxiosRequestConfig, +} from 'axios'; + +const MAX_REQUESTS_COUNT = 5; +let PENDING_REQUESTS = 0; + +const onRequest = (config: InternalAxiosRequestConfig): InternalAxiosRequestConfig => { + if (PENDING_REQUESTS < MAX_REQUESTS_COUNT) { + PENDING_REQUESTS++; + return config; + } +}; + +const onRequestError = (error: AxiosError): Promise => { + console.error(`[request error] [${JSON.stringify(error)}]`); + PENDING_REQUESTS--; + return Promise.reject(error); +}; + +const onResponse = (response: AxiosResponse) => { + PENDING_REQUESTS = Math.max(0, PENDING_REQUESTS - 1); + return response; +}; + +const onResponseError = (error: AxiosError) => { + PENDING_REQUESTS = Math.max(0, PENDING_REQUESTS - 1); + return error; +}; + +export const addInterceptors = (axiosInstance: AxiosInstance): AxiosInstance => { + axiosInstance.interceptors.request.use(onRequest, onRequestError); + axiosInstance.interceptors.response.use(onResponse, onResponseError); + return axiosInstance; +}; diff --git a/src/api/eosio_core.ts b/src/api/eosio_core.ts deleted file mode 100644 index 37e57ec0..00000000 --- a/src/api/eosio_core.ts +++ /dev/null @@ -1,88 +0,0 @@ -/* see https://github.com/greymass/eosio-core/blob/master/test/api.ts for documentation */ -import { - ABIDef, - ABISerializable, - Action, - ActionType, - API, - APIClient, - Name, - PublicKey, - Serializer, -} from '@wharfkit/session'; -import { useNetworksStore } from 'src/stores/networks'; -import { GetTableRowsParams } from 'src/types'; -import { Chain } from 'src/types/Chain'; - - -const networksStore = useNetworksStore(); -const chain: Chain = networksStore.getCurrentNetwork; - -const eosioCore = new APIClient({ - url: chain.getHyperionEndpoint(), -}); - -export const getAccount = async function ( - address: string, -): Promise { - if (address){ - return await eosioCore.v1.chain.get_account(address); - } -}; - -export const getKeyAccounts = async function ( - key: PublicKey, -): Promise<{ account_names: Name[] }> { - const response = await eosioCore.v1.history.get_key_accounts(key); - return response; -}; - -export const getTokenBalances = async function ( - address: string, -): Promise { - return await eosioCore.v1.chain.get_currency_balance('eosio.token', address); -}; - -export const getTableRows = async function ( - tableInput: GetTableRowsParams, -): Promise { - return await eosioCore.v1.chain.get_table_rows(tableInput); -}; - -export const deserializeActionData = async function ( - data: ActionType, -): Promise { - const { abi } = await eosioCore.v1.chain.get_abi(data.account); - if (!abi) { - throw new Error(`No ABI for ${String(data.account)}`); - } - const action = Action.from(data, abi); - // eslint-disable-next-line - return Serializer.objectify(action.decodeData(abi)); -}; - -export const deserializeActionDataFromAbi = function ( - data: ActionType, - abi: ABIDef, -): ABISerializable { - if (!abi) { - throw new Error(`No ABI for ${String(data.account)}`); - } - const action = Action.from(data, abi); - // eslint-disable-next-line - return Serializer.objectify(action.decodeData(abi)); -}; - -export const serializeActionData = async function ( - account: string, - name: string, - data: unknown, -): Promise { - const { abi } = await eosioCore.v1.chain.get_abi(account); - if (!abi) { - throw new Error(`No ABI for ${account}`); - } - - const { hexString } = Serializer.encode({ object: data, abi, type: name }); - return hexString; -}; diff --git a/src/api/hyperion.ts b/src/api/hyperion.ts index afa3e197..7af07ece 100644 --- a/src/api/hyperion.ts +++ b/src/api/hyperion.ts @@ -5,74 +5,34 @@ */ import axios, { AxiosRequestConfig } from 'axios'; +import { addInterceptors } from 'src/api/axiosInterceptors'; import { useNetworksStore } from 'src/stores/networks'; import { - ABI, AccountDetails, Action, ActionData, - Block, - ChainInfo, Get_actions, GetActionsResponse, - GetProducers, GetProposals, GetProposalsProps, PermissionLinks, PermissionLinksData, - ProducerSchedule, - TableByScope, Token, - Transaction, } from 'src/types'; import { AccountCreatorInfo, HyperionTransactionFilter } from 'src/types/Api'; import { Chain } from 'src/types/Chain'; - const networksStore = useNetworksStore(); const chain: Chain = networksStore.getCurrentNetwork; const hyperion = axios.create({ baseURL: chain.getHyperionEndpoint() }); -const controller = new AbortController(); -export const DEFAULT_ICON = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjciIGhlaWdodD0iMTgiIHZpZXdCb3g9IjAgMCAyNyAxOCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGNpcmNsZSBjeD0iMTgiIGN5PSI5IiByPSI4IiBmaWxsPSJ3aGl0ZSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLXdpZHRoPSIyIi8+CjxjaXJjbGUgY3g9IjkiIGN5PSI5IiByPSI4IiBmaWxsPSJ3aGl0ZSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLXdpZHRoPSIyIi8+Cjwvc3ZnPgo='; - +addInterceptors(hyperion); const name = chain.getName(); +export const DEFAULT_ICON = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjciIGhlaWdodD0iMTgiIHZpZXdCb3g9IjAgMCAyNyAxOCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGNpcmNsZSBjeD0iMTgiIGN5PSI5IiByPSI4IiBmaWxsPSJ3aGl0ZSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLXdpZHRoPSIyIi8+CjxjaXJjbGUgY3g9IjkiIGN5PSI5IiByPSI4IiBmaWxsPSJ3aGl0ZSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLXdpZHRoPSIyIi8+Cjwvc3ZnPgo='; + const url = `https://raw.githubusercontent.com/telosnetwork/token-list/main/tokens.${name}.json`; -const MAX_REQUESTS_COUNT = 5; -const INTERVAL_MS = 10; -let PENDING_REQUESTS = 0; - -/** - * Axios Request Interceptor - */ -hyperion.interceptors.request.use(function (config) { - return new Promise((resolve) => { - const interval = setInterval(() => { - if (PENDING_REQUESTS < MAX_REQUESTS_COUNT) { - PENDING_REQUESTS++; - clearInterval(interval); - resolve(config); - } - }, INTERVAL_MS); - }); -}); - -/** - * Axios Response Interceptor - */ -hyperion.interceptors.response.use( - function (response) { - PENDING_REQUESTS = Math.max(0, PENDING_REQUESTS - 1); - return Promise.resolve(response); - }, - function (error) { - PENDING_REQUESTS = Math.max(0, PENDING_REQUESTS - 1); - return Promise.reject(error); - }, -); - export const getHyperionAccountData = async function ( address: string, ): Promise { @@ -174,18 +134,6 @@ export const getTransaction = async function ( return response.data; }; -export const getTransactionV1 = async function ( - id?: string, -): Promise { - const response = await hyperion.post( - 'v1/history/get_transaction', - { - id: id, - }, - ); - return response.data; -}; - export const getChildren = async function ( address?: string, ): Promise { @@ -214,52 +162,27 @@ export const getPermissionLinks = async function ( return response.data.links; }; -export const getTableByScope = async function ( - data: unknown, -): Promise { - const response = await hyperion.post('v1/chain/get_table_by_scope', data); - return (response.data as {rows:TableByScope[]}).rows; -}; - -export const getBlock = async function (block: string): Promise { - controller.abort(); - const response = await hyperion.post('v1/chain/get_block', { - block_num_or_id: block, - signal: controller.signal, - }); - return response.data as Block; -}; - export const getActions = async function ( account: string, filter: string, limit?: number, skip?: number, ): Promise { - controller.abort(); - const response = await hyperion.get('v2/history/get_actions', { - params: { - account, - filter, - limit, - skip, + const signal = new AbortController().signal; + const response = await hyperion.get('v2/history/get_actions', + { + params: { + account, + filter, + limit, + skip, + }, + signal, }, - }); + ); return response.data as Get_actions; }; -export const getInfo = async function (): Promise { - controller.abort(); - const response = await hyperion.get('v1/chain/get_info'); - return response.data as ChainInfo; -}; - -export const getSchedule = async function (): Promise { - controller.abort(); - const response = await hyperion.get('v1/chain/get_producer_schedule'); - return response.data as ProducerSchedule; -}; - export const getProposals = async function ({ proposer, proposal, @@ -283,21 +206,6 @@ export const getProposals = async function ({ return response.data as GetProposals; }; -export const getProducers = async function (): Promise { - const response = await hyperion.post('v1/chain/get_producers', { - json: true, - limit: 10000, - }); - return response.data as GetProducers; -}; - -export const getABI = async function (account: string): Promise { - const response = await hyperion.post('v1/chain/get_abi', { - account_name: account, - }); - return response.data as ABI; -}; - export const getHyperionKeyAccounts = async function ( key: string, ): Promise<{ account_names: string[] }> { @@ -307,9 +215,4 @@ export const getHyperionKeyAccounts = async function ( return response.data as { account_names: string[] }; }; -export const getProducerSchedule = async function (): Promise<{ - active: { producers: { producer_name: string }[] }; -}> { - const response = await hyperion.get('v1/chain/get_producer_schedule'); - return response.data as { active: { producers: { producer_name: string }[] } }; -}; + diff --git a/src/api/index.ts b/src/api/index.ts index 990bffd0..89308d6a 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -5,39 +5,38 @@ * - for existing method enpoint, export from service file and update the import statement here */ import { - getHyperionAccountData, - getCreator, - getTokens, - getTransactions, - getTransaction, - getTransactionV1, + getActions, getChildren, + getCreator, + getHyperionAccountData, getPermissionLinks, - getTableByScope, - getBlock, - getActions, - getInfo, - getSchedule, getProposals, - getProducers, - getABI, - getProducerSchedule, + getTokens, + getTransaction, + getTransactions, } from 'src/api/hyperion'; // e.g. './new-service' method name stays the same import { + deserializeActionData, + getABI, getAccount, - getKeyAccounts, + getAccountsByPublicKey, + getBlock, + getInfo, + getProducers, + getProducerSchedule, + getTableByScope, getTableRows, getTokenBalances, - deserializeActionData, + getTransactionV1, serializeActionData, -} from 'src/api/eosio_core'; +} from 'src/api/antelopeV1'; import { getApy } from 'src/api/telosApi'; export const api = { getAccount, - getKeyAccounts, + getAccountsByPublicKey, getHyperionAccountData, getCreator, getTokens, @@ -52,12 +51,11 @@ export const api = { getBlock, getActions, getInfo, - getSchedule, + getProducerSchedule, getProposals, getProducers, getABI, deserializeActionData, serializeActionData, - getProducerSchedule, getApy, }; diff --git a/src/api/telosApi.ts b/src/api/telosApi.ts index 6e7b89fc..390c4d60 100644 --- a/src/api/telosApi.ts +++ b/src/api/telosApi.ts @@ -1,43 +1,18 @@ import axios from 'axios'; +import { addInterceptors } from 'src/api/axiosInterceptors'; import { useNetworksStore } from 'src/stores/networks'; -import { Chain } from 'src/types/Chain'; - -const MAX_REQUESTS_COUNT = 5; -const INTERVAL_MS = 10; -let PENDING_REQUESTS = 0; const networksStore = useNetworksStore(); -const chain: Chain = networksStore.getCurrentNetwork; - -const telosApi = axios.create({ baseURL: chain.getApiEndpoint() }); - -telosApi.interceptors.request.use(function (config) { - return new Promise((resolve) => { - const interval = setInterval(() => { - if (PENDING_REQUESTS < MAX_REQUESTS_COUNT) { - PENDING_REQUESTS++; - clearInterval(interval); - resolve(config); - } - }, INTERVAL_MS); - }); -}); +const networkEndpoint = networksStore.getCurrentNetwork.getNetworkEndpoint(); -/** - * Axios Response Interceptor - */ -telosApi.interceptors.response.use( - function (response) { - PENDING_REQUESTS = Math.max(0, PENDING_REQUESTS - 1); - return Promise.resolve(response); - }, - function (error) { - PENDING_REQUESTS = Math.max(0, PENDING_REQUESTS - 1); - return Promise.reject(error); - }, -); +const telosApi = axios.create({ baseURL: networkEndpoint }); +addInterceptors(telosApi); export const getApy = async function (): Promise { - const response = await telosApi.get('apy/native'); - return response.data as string; + try { + const response = await telosApi.get('v1/apy/native'); + return response.data as string; + } catch (e) { + console.error('Error on apy/native', e); + } }; diff --git a/src/api/wharf.ts b/src/api/wharf.ts index f3d06a26..b0ab5042 100644 --- a/src/api/wharf.ts +++ b/src/api/wharf.ts @@ -15,7 +15,7 @@ export const kit = new SessionKit({ chains: [ { id: networksStore.getCurrentNetwork.getChainId(), - url: String(networksStore.getCurrentNetwork.getRPCEndpoint()), + url: String(networksStore.getCurrentNetwork.getV1Endpoint()), }, ], ui, diff --git a/src/components/AccountSearch.vue b/src/components/AccountSearch.vue index 315a6aea..51686b7b 100644 --- a/src/components/AccountSearch.vue +++ b/src/components/AccountSearch.vue @@ -1,8 +1,9 @@ diff --git a/src/components/transaction/AccountFormat.vue b/src/components/transaction/AccountFormat.vue index e2b2d099..c28d1174 100644 --- a/src/components/transaction/AccountFormat.vue +++ b/src/components/transaction/AccountFormat.vue @@ -24,7 +24,7 @@ export default defineComponent({