From b3c26dfaf8b1782ef5a85eccc6966b4512f4a13c Mon Sep 17 00:00:00 2001 From: 021-projects <20326979+021-projects@users.noreply.github.com> Date: Fri, 22 Sep 2023 14:39:55 +0300 Subject: [PATCH 1/4] feat(ui-tabs): add support for subtabs --- src/js/components/UiTabs.vue | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/js/components/UiTabs.vue b/src/js/components/UiTabs.vue index 8a3e897..779c9cb 100644 --- a/src/js/components/UiTabs.vue +++ b/src/js/components/UiTabs.vue @@ -19,7 +19,7 @@
@@ -59,7 +59,11 @@ export default { $route: { immediate: true, handler({ hash }) { - this.activeTab = (hash || this.tabs[0].key).replace('#', ''); + const cleanedHash = hash?.replace('#', ''); + + const tab = this.findTabByKey(cleanedHash); + + this.activeTab = (tab?.key || this.tabs[0].key); }, }, @@ -69,6 +73,18 @@ export default { } }, }, + + methods: { + findTabByKey ( key ) { + return this.tabs.find(tab => { + if (tab?.subtabKey && key.startsWith(tab?.subtabKey)) { + return true; + } + + return tab.key === key; + }); + } + } }; From 5544560bc38935579a80c7eadac8e83b16d0cdf4 Mon Sep 17 00:00:00 2001 From: 021-projects <20326979+021-projects@users.noreply.github.com> Date: Sat, 23 Sep 2023 12:41:16 +0300 Subject: [PATCH 2/4] chore(tonapi): update api to v2, add getAccount method, refactor --- src/js/api/tonapi.js | 16 +++++++++------- src/js/config.js | 4 +++- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/js/api/tonapi.js b/src/js/api/tonapi.js index 9049c58..5add84c 100644 --- a/src/js/api/tonapi.js +++ b/src/js/api/tonapi.js @@ -1,4 +1,4 @@ -import { TONAPI_ENDPOINT, TONAPI_KEY } from '~/config.js'; +import { TONAPI_ENDPOINT } from '~/config.js'; import { canonizeAddress } from '~/tonweb.js'; import axios from 'axios'; @@ -11,11 +11,9 @@ const http = axios.create({ * @return {Promise} */ export const getJettonBalances = async function(address) { - const response = await http.get('jetton/getBalances', { params: { - account: address, - }}); + const response = await http.get(`accounts/${address}/jettons`); - const balances = (response.data.balances || []).map((balance) => Object.freeze({ + return (response.data.balances || []).map(( balance ) => Object.freeze({ address: canonizeAddress(balance.wallet_address.address), balance: balance.balance, jetton_address: canonizeAddress(balance.jetton_address), @@ -32,6 +30,10 @@ export const getJettonBalances = async function(address) { }), }), })); - - return balances; }; + +export const getAccount = async function(address) { + const response = await http.get(`accounts/${address}`); + + return response.data; +} diff --git a/src/js/config.js b/src/js/config.js index e0393b0..369c476 100644 --- a/src/js/config.js +++ b/src/js/config.js @@ -5,13 +5,15 @@ export const IS_TESTNET = process.env.TESTNET || false; export const ADDRBOOK_LOCATION = process.env.ADDRBOOK_LOCATION || 'https://catchain.github.io/tonscan/src/addrbook.json'; export const COINGECKO_ENDPOINT = 'https://api.coingecko.com/api/v3'; export const DEV_EXPLORER_ADDRESS = process.env.DEV_EXPLORER_ADDRESS || 'https://explorer.toncoin.org'; +// Todo: This is not actually an endpoint for the TonCenter API, const must be renamed export const TONCENTER_API_ENDPOINT = process.env.TONCENTER_API_ENDPOINT || 'https://api.ton.cat/v2/explorer'; +export const TONCENTER_API_JSON_RPC_ENDPOINT = process.env.TONCENTER_API_JSON_RPC_ENDPOINT || 'https://toncenter.com/api/v2/jsonRPC'; export const TONCENTER_API_KEY = process.env.TONCENTER_API_KEY; export const TONCENTER_INDEX_API_ENDPOINT = process.env.TONCENTER_INDEX_API_ENDPOINT || 'https://toncenter.com/api/index'; export const TONCENTER_INDEX_API_KEY = process.env.TONCENTER_INDEX_API_KEY || TONCENTER_API_KEY; export const EXTENDER_CONTRACTS_API_ENDPOINT = process.env.EXTENDER_CONTRACTS_API_ENDPOINT || 'https://api.ton.cat/v2/explorer/nft'; export const GETGEMS_GRAPHQL_ENDPOINT = process.env.GETGEMS_GRAPHQL_ENDPOINT || 'https://api.getgems.io/graphql'; -export const TONAPI_ENDPOINT = process.env.TONAPI_ENDPOINT || 'https://tonapi.io/v1'; +export const TONAPI_ENDPOINT = process.env.TONAPI_ENDPOINT || 'https://tonapi.io/v2'; export const TONAPI_KEY = process.env.TONAPI_KEY; export const TYPESENSE_API_KEY = process.env.TYPESENSE_API_KEY; From 722f28997b1bc5f3e1a75bc5b7af75ae4d41871d Mon Sep 17 00:00:00 2001 From: 021-projects <20326979+021-projects@users.noreply.github.com> Date: Sat, 23 Sep 2023 12:41:42 +0300 Subject: [PATCH 3/4] feat(components): add ui-select component --- src/js/components/UiSelect.vue | 195 +++++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 src/js/components/UiSelect.vue diff --git a/src/js/components/UiSelect.vue b/src/js/components/UiSelect.vue new file mode 100644 index 0000000..c2a1393 --- /dev/null +++ b/src/js/components/UiSelect.vue @@ -0,0 +1,195 @@ + + + + + From 0b0b59376797b3bd33216f412f8c91f6aa280d54 Mon Sep 17 00:00:00 2001 From: 021-projects <20326979+021-projects@users.noreply.github.com> Date: Sat, 23 Sep 2023 12:50:12 +0300 Subject: [PATCH 4/4] feat(contract): get methods, ton lib added --- package.json | 4 + src/js/components/address/AddressTabs.vue | 8 +- .../address/Verifier/VerifierGetMethods.vue | 297 ++++++++++++++++++ .../address/Verifier/VerifierTabs.vue | 81 +++++ src/js/directives/outsideClick.js | 16 + src/js/i18n/en.js | 14 +- src/js/ton.js | 16 + src/sass/app.scss | 76 ++++- webpack.config.js | 5 + 9 files changed, 511 insertions(+), 6 deletions(-) create mode 100644 src/js/components/address/Verifier/VerifierGetMethods.vue create mode 100644 src/js/components/address/Verifier/VerifierTabs.vue create mode 100644 src/js/directives/outsideClick.js create mode 100644 src/js/ton.js diff --git a/package.json b/package.json index 7134ecb..44dca7a 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@primer/octicons": "^17.11.1", "axios": "^0.24.0", "axios-retry": "^3.2.5", + "buffer": "^6.0.3", "chart.js": "^4.0.1", "chartjs-adapter-date-fns": "^2.0.1", "chartjs-plugin-gradient": "^0.5.1", @@ -28,6 +29,9 @@ "highlightjs-func": "^0.3.1", "qrcode.vue": "^1.7.0", "timeago.js": "^4.0.2", + "ton": "^13.6.1", + "ton-core": "^0.52.0", + "ton-crypto": "^3.2.0", "vue": "^2.6.14", "vue-clipboard2": "^0.3.3", "vue-i18n": "^8.26.8", diff --git a/src/js/components/address/AddressTabs.vue b/src/js/components/address/AddressTabs.vue index 5690dfc..fdeea4c 100644 --- a/src/js/components/address/AddressTabs.vue +++ b/src/js/components/address/AddressTabs.vue @@ -5,8 +5,7 @@ import IconNft from '@img/icons/material-duotone/view-in-ar.svg?inline'; import IconToken from '@img/icons/material-duotone/toll.svg?inline'; import TabUserNfts from './TabUserNfts.vue'; import TabUserTokens from './TabUserTokens.vue'; -import TabContractSources from './Verifier/Verifier.vue'; -import ContractInfo from './ContractInfo.vue'; +import TabContractTabs from './Verifier/VerifierTabs.vue'; import TxHistory from './TxHistory.vue'; import UiTabs from '~/components/UiTabs.vue'; @@ -57,12 +56,13 @@ export default { icon: IconToken, content: { key, props, component: TabUserTokens }, }, { - key: 'source', + key: 'contract_source', + subtabKey: 'contract_', text: this.$t('address.tab_contract'), icon: IconContract, content: { key, - component: TabContractSources, + component: TabContractTabs, props: { address: this.address, isActive: this.isActive, diff --git a/src/js/components/address/Verifier/VerifierGetMethods.vue b/src/js/components/address/Verifier/VerifierGetMethods.vue new file mode 100644 index 0000000..f45f38c --- /dev/null +++ b/src/js/components/address/Verifier/VerifierGetMethods.vue @@ -0,0 +1,297 @@ + + + + + diff --git a/src/js/components/address/Verifier/VerifierTabs.vue b/src/js/components/address/Verifier/VerifierTabs.vue new file mode 100644 index 0000000..2e2a667 --- /dev/null +++ b/src/js/components/address/Verifier/VerifierTabs.vue @@ -0,0 +1,81 @@ + + + diff --git a/src/js/directives/outsideClick.js b/src/js/directives/outsideClick.js new file mode 100644 index 0000000..520d7b6 --- /dev/null +++ b/src/js/directives/outsideClick.js @@ -0,0 +1,16 @@ +// Taken from https://stackoverflow.com/a/42389266 +export default { + bind: function ( el, binding, vnode ) { + el.clickOutsideEvent = function ( event ) { + // here I check that click was outside the el and his children + if (!(el === event.target || el.contains(event.target))) { + // and if it did, call method provided in attribute value + vnode.context[binding.expression](event); + } + }; + document.body.addEventListener('click', el.clickOutsideEvent) + }, + unbind: function ( el ) { + document.body.removeEventListener('click', el.clickOutsideEvent) + } +} diff --git a/src/js/i18n/en.js b/src/js/i18n/en.js index fef6831..9002c7a 100644 --- a/src/js/i18n/en.js +++ b/src/js/i18n/en.js @@ -19,6 +19,18 @@ export default { }, }, + contract: { + tab_source: 'Source', + tab_get_methods: 'Get methods', + tab_address_formats: 'Address formats', + call_method: 'Call method', + arbitrary_method: 'Arbitrary method', + enter_method_name: 'Enter method name', + execute: 'Execute', + add_argument: 'Add argument', + value: 'Value', + }, + address: { info: { address: 'Address', @@ -73,7 +85,7 @@ export default { tab_nfts: 'NFTs', tab_transactions: 'History', tab_contract: 'Contract', - tab_tokens: 'Jettons', + tab_tokens: 'Jettons', tx_table: { empty: 'No transaction history', diff --git a/src/js/ton.js b/src/js/ton.js new file mode 100644 index 0000000..7e352a3 --- /dev/null +++ b/src/js/ton.js @@ -0,0 +1,16 @@ +import { Address, TonClient } from 'ton'; +import { TONCENTER_API_JSON_RPC_ENDPOINT, TONCENTER_API_KEY } from '~/config.js'; + +const client = new TonClient({ + endpoint: TONCENTER_API_JSON_RPC_ENDPOINT, + apiKey: TONCENTER_API_KEY +}); + +export const runGetMethod = async function ( address, method, args ) { + // todo: need builder for args + + return await client.runMethod( + Address.parse(address), + method, + ); +} diff --git a/src/sass/app.scss b/src/sass/app.scss index 5602926..3311758 100644 --- a/src/sass/app.scss +++ b/src/sass/app.scss @@ -80,6 +80,8 @@ $blueBright: #0069ff; --chart-grid-color: #2a2a2a; --chart-pie-border-color: var(--card-background); --chart-skeleton-background-color: #2c2c2c; //var(--body-light-muted-color); + + --input-bg: #232323; } @media (prefers-color-scheme: dark) { @@ -159,7 +161,7 @@ $blueBright: #0069ff; html { font-size: 22px; } - + @media screen and (max-width: 479px) { html { font-size: 14px; @@ -230,6 +232,78 @@ input[type=search] { } } +.btn-material { + color: #FFF; + font-weight: 500; + padding: 10px 15px; + font-size: 14px; + text-transform: uppercase; + border-radius: 6px; + text-align: center; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: .15s background ease; + text-decoration: none; + background: var(--bg-color); + &:hover { + text-decoration: none; + background: var(--bg-color-hover); + } + &--blue { + --bg-color: var(--big-blue-button-background); + --bg-color-hover: var(--big-blue-button-hover-background); + } + &--grey { + --bg-color: var(--body-light-muted-color); + --bg-color-hover: rgba(209, 217, 234, 0.14); + } + &--disabled { + --bg-color: var(--big-blue-button-disabled-background); + color: var(--big-blue-button-disabled-color); + pointer-events: none; + } + + .icon { + width: 18px; + height: 18px; + margin-right: 8px; + fill: currentColor; + } +} + +.input-group { + display: flex; + flex-direction: row; + &--full { + flex: 1 1 auto; + } + .input-material { + &:first-child { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-right: 1px solid var(--body-light-muted-color); + } + &:last-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + } +} + +.input-material { + padding: 9px; + background: var(--input-bg); + color: var(--body-text-color); + border-radius: 6px; + border: 2px solid transparent; + transition: 0.2s all ease; + &::placeholder { + color: var(--body-muted-text-color); + } +} + h1, h2, h3, h4, h5, h6, strong, b { font-weight: 500; } diff --git a/webpack.config.js b/webpack.config.js index 0043554..08b92f9 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -2,6 +2,7 @@ const dotenv = require('dotenv').config({ path: __dirname + '/.env' + (process.e const Encore = require('@symfony/webpack-encore'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const path = require('path'); +const webpack = require('webpack'); if (!Encore.isRuntimeEnvironmentConfigured()) { Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev'); @@ -56,6 +57,10 @@ Encore.addPlugin(new HtmlWebpackPlugin({ }, })); +Encore.addPlugin(new webpack.ProvidePlugin({ + Buffer: ['buffer', 'Buffer'], +})) + Encore.isProduction() && Encore.enableVersioning(); Encore.isProduction() && Encore.cleanupOutputBeforeBuild();