From 5a4c515ab634f5fb8a9eae262a38dc67d09f2846 Mon Sep 17 00:00:00 2001 From: cong_wang Date: Thu, 20 Jun 2024 11:52:47 +0800 Subject: [PATCH] feat: Add Phalcon Explorer shortcut for Solana txns --- CHANGELOG.md | 7 +- package.json | 2 +- src/background/index.ts | 23 +++-- .../components/icon/IconPhalcon/index.tsx | 2 +- src/common/constants/event.ts | 1 + src/common/constants/scan.ts | 6 +- src/common/constants/support.ts | 5 ++ .../components/ParsersButton/index.tsx | 34 ++++++++ src/content/solanaexpl/components/index.ts | 1 + src/content/solanaexpl/feat-scripts/index.ts | 2 + .../solanaexpl/feat-scripts/quick2parsers.tsx | 17 ++++ .../transaction-hash-phalcon-link.tsx | 85 +++++++++++++++++++ src/content/solanaexpl/index.tsx | 19 +---- .../solanaexpl/page-scripts/address.tsx | 27 +++--- src/content/solanaexpl/page-scripts/tx.tsx | 8 +- .../components/ParsersButton/index.tsx | 28 ++++++ src/content/solanafm/components/index.ts | 1 + src/content/solanafm/feat-scripts/index.ts | 2 + .../solanafm/feat-scripts/quick2parsers.tsx | 17 ++++ .../transaction-hash-phalcon-link.tsx | 83 ++++++++++++++++++ src/content/solanafm/helper.ts | 4 +- src/content/solanafm/index.tsx | 19 +---- src/content/solanafm/page-scripts/address.tsx | 33 ++++--- src/content/solanafm/page-scripts/tx.tsx | 8 +- .../components/ParsersButton/index.tsx | 28 ++++++ src/content/solscan/components/index.ts | 1 + src/content/solscan/feat-scripts/index.ts | 2 + .../solscan/feat-scripts/quick2parsers.tsx | 20 +++++ .../transaction-hash-phalcon-link.tsx | 85 +++++++++++++++++++ src/content/solscan/helper.ts | 5 +- src/content/solscan/main.tsx | 6 +- src/content/solscan/page-scripts/account.tsx | 14 ++- src/content/solscan/page-scripts/block.tsx | 34 ++++++++ src/content/solscan/page-scripts/index.ts | 1 + src/content/solscan/page-scripts/tx.tsx | 19 +++-- 35 files changed, 563 insertions(+), 86 deletions(-) create mode 100644 src/content/solanaexpl/components/ParsersButton/index.tsx create mode 100644 src/content/solanaexpl/feat-scripts/quick2parsers.tsx create mode 100644 src/content/solanaexpl/feat-scripts/transaction-hash-phalcon-link.tsx create mode 100644 src/content/solanafm/components/ParsersButton/index.tsx create mode 100644 src/content/solanafm/feat-scripts/quick2parsers.tsx create mode 100644 src/content/solanafm/feat-scripts/transaction-hash-phalcon-link.tsx create mode 100644 src/content/solscan/components/ParsersButton/index.tsx create mode 100644 src/content/solscan/feat-scripts/quick2parsers.tsx create mode 100644 src/content/solscan/feat-scripts/transaction-hash-phalcon-link.tsx create mode 100644 src/content/solscan/page-scripts/block.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d562aa..60852ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,12 @@ +### v5.1.0 + +- [feat] Add shortcut to navigate Solana transaction hashes to Phalcon Explorer +- [fix] Fix extension functionality issue caused by solana.fm website update + ### v5.0.7 -- [fix] Remove the copy button for the new version of Etherscan transaction hash, and readapt some of the copy block buttons - [feat] Adapted to the new version of basescan +- [fix] Remove the copy button for the new version of Etherscan transaction hash, and readapt some of the copy block buttons ### v5.0.6 diff --git a/package.json b/package.json index cc53330..f2d27ae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "metasuites", - "version": "5.0.7", + "version": "5.1.0", "repository": { "type": "git", "url": "https://github.com/blocksecteam/metasuites.git" diff --git a/src/background/index.ts b/src/background/index.ts index 23e4877..259c214 100644 --- a/src/background/index.ts +++ b/src/background/index.ts @@ -15,7 +15,8 @@ import { GET_SOLANAFM_ACCOUNT_INFO, GET_SOLANAFM_ACCOUNT_TRANSFERS, GET_SOLSCAN_ACCOUNT_TAB_DATA, - GET_SOLSCAN_TRANSACTION + GET_SOLSCAN_TRANSACTION, + GET_SOLSCAN_BLOCK_TXS } from '@common/constants' import { initBackgroundRequest } from './listeners' @@ -168,9 +169,11 @@ browser.webRequest.onCompleted.addListener( 'https://api.solscan.io/v2/account/stake?*', 'https://api-v2.solscan.io/v2/account/activity/dextrading?*', 'https://api-v2.solscan.io/v2/token/transfer?address=*', + 'https://api-v2.solscan.io/v2/account/transfer?address=*', 'https://api.solscan.io/v2/account/transaction?address=*', 'https://api.solscan.io/v2/token/holders?token=*', - 'https://api-v2.solscan.io/v2/token/activity/dextrading/total?address=*' + 'https://api-v2.solscan.io/v2/token/activity/dextrading/total?address=*', + 'https://api.solscan.io/v2/account/token/txs?address=*' ] } ) @@ -193,6 +196,18 @@ browser.webRequest.onCompleted.addListener( async details => { const { tabId, method } = details if (tabId && method === 'GET') { + browser.tabs.sendMessage(tabId, GET_SOLSCAN_BLOCK_TXS).catch(() => void 0) + } + }, + { + urls: ['https://api.solscan.io/v2/block/txs?*'] + } +) + +browser.webRequest.onCompleted.addListener( + async details => { + const { tabId, method } = details + if (tabId && method === 'POST') { browser.tabs .sendMessage(tabId, GET_SOLANAFM_ACCOUNT_INFO) .catch(() => void 0) @@ -213,9 +228,7 @@ browser.webRequest.onCompleted.addListener( } }, { - urls: [ - 'https://api.solana.fm/v0/accounts/BNLtpXLqsjDGxzB1Mmcv3NmEiQhSSWFq8JViKrrrQ8Do/transfers?*' - ] + urls: ['https://api.solana.fm/v0/accounts/*/transfers?*'] } ) diff --git a/src/common/components/icon/IconPhalcon/index.tsx b/src/common/components/icon/IconPhalcon/index.tsx index 0a5516c..361bc48 100644 --- a/src/common/components/icon/IconPhalcon/index.tsx +++ b/src/common/components/icon/IconPhalcon/index.tsx @@ -64,7 +64,7 @@ export default function IconPhalcon({ > = ({ txHash }) => { + const chain = getChainSimpleName() + + const pathname = PHALCON_SUPPORT_LIST.find( + item => item.chain === chain + )?.pathname + + const handleClick = (e: React.MouseEvent) => { + e.preventDefault() + window.open(`${PHALCON_EXPLORER_DOMAIN}/tx/${pathname}/${txHash}`, '_blank') + } + + if (!chain) return null + return ( + + ) +} + +export default ParsersBtn diff --git a/src/content/solanaexpl/components/index.ts b/src/content/solanaexpl/components/index.ts index 2915896..1b3141b 100644 --- a/src/content/solanaexpl/components/index.ts +++ b/src/content/solanaexpl/components/index.ts @@ -2,3 +2,4 @@ export { default as PhalconExplorerButton } from './PhalconExplorerButton' export { default as FundFlowButton } from './FundFlowButton' export { default as MainAddressLabel } from './MainAddressLabel' export { default as MainPrivateLabel } from './MainPrivateLabel' +export { default as ParsersButton } from './ParsersButton' diff --git a/src/content/solanaexpl/feat-scripts/index.ts b/src/content/solanaexpl/feat-scripts/index.ts index 4407a1d..2fe065c 100644 --- a/src/content/solanaexpl/feat-scripts/index.ts +++ b/src/content/solanaexpl/feat-scripts/index.ts @@ -1,3 +1,5 @@ export { default as renderFundFlowButton } from './fund-flow' export { default as renderMainAddressLabel } from './main-address-label' export { default as renderTxPageAddressLabels } from './tx-address-labels' +export { default as renderAlternativeParsers } from './quick2parsers' +export { default as renderTransactionHashPhalconLink } from './transaction-hash-phalcon-link' diff --git a/src/content/solanaexpl/feat-scripts/quick2parsers.tsx b/src/content/solanaexpl/feat-scripts/quick2parsers.tsx new file mode 100644 index 0000000..0253c39 --- /dev/null +++ b/src/content/solanaexpl/feat-scripts/quick2parsers.tsx @@ -0,0 +1,17 @@ +import { createRoot } from 'react-dom/client' +import $ from 'jquery' + +import { ParsersButton } from '../components' + +const renderAlternativeParsers = async () => { + const container = $( + '.card table .list tr:first-of-type > td:last-child > div' + ).first() + const txHash = container.text().trim() + const rootEl = $('') + container.prepend(rootEl) + + createRoot(rootEl[0]).render() +} + +export default renderAlternativeParsers diff --git a/src/content/solanaexpl/feat-scripts/transaction-hash-phalcon-link.tsx b/src/content/solanaexpl/feat-scripts/transaction-hash-phalcon-link.tsx new file mode 100644 index 0000000..f66b016 --- /dev/null +++ b/src/content/solanaexpl/feat-scripts/transaction-hash-phalcon-link.tsx @@ -0,0 +1,85 @@ +import React, { type FC, type ReactNode } from 'react' +import isMobile from 'is-mobile' +import { createRoot } from 'react-dom/client' + +import { PHALCON_SUPPORT_LIST } from '@common/constants' +import { getChainSimpleName } from '@common/utils' +import { PHALCON_EXPLORER_DOMAIN } from '@common/config/uri' +import { IconPhalcon } from '@common/components' + +const PhalconExplorerButton: FC<{ hash: string }> = ({ hash }) => { + const chain = getChainSimpleName() + + const pathname = PHALCON_SUPPORT_LIST.find( + item => item.chain === chain + )?.pathname + + const handleClick = (e: React.MouseEvent) => { + e.preventDefault() + window.open(`${PHALCON_EXPLORER_DOMAIN}/tx/${pathname}/${hash}`, '_blank') + } + + if (!chain) return null + return +} + +const appendIconToElement = (el: HTMLElement, reactNode: ReactNode) => { + if (!isMobile()) { + el.onmouseover = () => { + const btnEls = el.querySelectorAll( + '.__metadock-copy-address-btn__' + ) + if (btnEls.length) { + btnEls.forEach(btnEl => { + btnEl.style.display = 'inline-flex' + }) + } + } + el.onmouseout = () => { + const btnEls = el.querySelectorAll( + '.__metadock-copy-address-btn__' + ) + if (btnEls.length) { + btnEls.forEach(btnEl => { + btnEl.style.display = 'none' + }) + } + } + } + + el.setAttribute( + 'style', + 'padding-right:20px;position:relative;width:fit-content;' + ) + + const rootEl = document.createElement('span') + rootEl.classList.add('__metadock-copy-address-btn__') + rootEl.setAttribute( + 'style', + `position:absolute;right:0;display:${ + isMobile() ? 'inline-flex' : 'none' + };top: 50%;transform: translateY(-50%)` + ) + el?.appendChild(rootEl) + createRoot(rootEl).render(reactNode) +} + +const renderTransactionHashPhalconLink = async () => { + const txnTags = document.querySelectorAll( + "table a[href^='/tx/']" + ) + for (let i = 0; i < txnTags.length; i++) { + const el = txnTags[i] + const href = el.getAttribute('href') + if (!href) continue + const txnHash = href.substring(href.lastIndexOf('/') + 1) + let targetEl: HTMLElement | null = null + if (!el.parentElement) return + targetEl = el.parentElement.parentElement + if (targetEl && txnHash) { + appendIconToElement(targetEl, ) + } + } +} + +export default renderTransactionHashPhalconLink diff --git a/src/content/solanaexpl/index.tsx b/src/content/solanaexpl/index.tsx index 693d19f..590b944 100644 --- a/src/content/solanaexpl/index.tsx +++ b/src/content/solanaexpl/index.tsx @@ -1,32 +1,15 @@ import browser from 'webextension-polyfill' import '@common/styles/inject.common' -import { getPageName, isHashItemsSimilar } from '@common/utils' import { URL_UPDATED } from '@common/constants' import execute from './main' export const initSolanaExplorer = async () => { execute() - let pageName = getPageName(window.location.hash.substring(1)) - let originURL = window.location.href browser.runtime.onMessage.addListener((message, _sender, sendResponse) => { if (message === URL_UPDATED) { - const newPageName = getPageName() - if (newPageName !== pageName) { - execute() - } else { - const isSimilar = isHashItemsSimilar( - new URL(originURL).hash, - window.location.hash, - 3 - ) - if (!isSimilar) { - execute() - } - } - originURL = window.location.href - pageName = newPageName + execute() sendResponse() } }) diff --git a/src/content/solanaexpl/page-scripts/address.tsx b/src/content/solanaexpl/page-scripts/address.tsx index d9654c8..05e2480 100644 --- a/src/content/solanaexpl/page-scripts/address.tsx +++ b/src/content/solanaexpl/page-scripts/address.tsx @@ -3,7 +3,11 @@ import $ from 'jquery' import { store } from '@src/store' import { lazyLoad } from '../helper' -import { renderFundFlowButton, renderMainAddressLabel } from '../feat-scripts' +import { + renderFundFlowButton, + renderMainAddressLabel, + renderTransactionHashPhalconLink +} from '../feat-scripts' const createAccountBox = () => { if ($('#md-account-box').length) return Promise.reject() @@ -26,17 +30,16 @@ const createAccountBox = () => { } const initAddressPageScript = async () => { - const { fundFlow, enhancedLabels } = await store.get('options') - lazyLoad( - () => - createAccountBox().then(() => { - if (fundFlow) renderFundFlowButton() - if (enhancedLabels) { - renderMainAddressLabel() - } - }), - `body > div.main-content > div:nth-of-type(3) > div:nth-of-type(2) > div > span[class*="spinner"]` - ) + const { fundFlow, enhancedLabels, quick2Parsers } = await store.get('options') + lazyLoad(() => { + createAccountBox().then(() => { + if (fundFlow) renderFundFlowButton() + if (enhancedLabels) { + renderMainAddressLabel() + } + }) + if (quick2Parsers) renderTransactionHashPhalconLink() + }, 'span[class*="spinner"]:not(td >*)') } export default initAddressPageScript diff --git a/src/content/solanaexpl/page-scripts/tx.tsx b/src/content/solanaexpl/page-scripts/tx.tsx index c8452dc..2bf8739 100644 --- a/src/content/solanaexpl/page-scripts/tx.tsx +++ b/src/content/solanaexpl/page-scripts/tx.tsx @@ -1,12 +1,16 @@ import { store } from '@src/store' -import { renderTxPageAddressLabels } from '../feat-scripts' +import { + renderTxPageAddressLabels, + renderAlternativeParsers +} from '../feat-scripts' import { lazyLoad } from '../helper' const initTxPageScript = async () => { - const { enhancedLabels } = await store.get('options') + const { enhancedLabels, quick2Parsers } = await store.get('options') lazyLoad(() => { if (enhancedLabels) renderTxPageAddressLabels() + if (quick2Parsers) renderAlternativeParsers() }, `body > div.main-content > div:nth-of-type(3) > div > div > span[class*="spinner"]`) } diff --git a/src/content/solanafm/components/ParsersButton/index.tsx b/src/content/solanafm/components/ParsersButton/index.tsx new file mode 100644 index 0000000..5d5f3b1 --- /dev/null +++ b/src/content/solanafm/components/ParsersButton/index.tsx @@ -0,0 +1,28 @@ +import React, { type FC } from 'react' + +import { PHALCON_EXPLORER_DOMAIN } from '@common/config/uri' +import { getChainSimpleName } from '@common/utils' +import { PHALCON_SUPPORT_LIST } from '@common/constants' +import { IconPhalcon } from '@common/components' + +interface Props { + txHash: string +} + +const ParsersBtn: FC = ({ txHash }) => { + const chain = getChainSimpleName() + + const pathname = PHALCON_SUPPORT_LIST.find( + item => item.chain === chain + )?.pathname + + const handleClick = (e: React.MouseEvent) => { + e.preventDefault() + window.open(`${PHALCON_EXPLORER_DOMAIN}/tx/${pathname}/${txHash}`, '_blank') + } + + if (!chain) return null + return +} + +export default ParsersBtn diff --git a/src/content/solanafm/components/index.ts b/src/content/solanafm/components/index.ts index 2915896..1b3141b 100644 --- a/src/content/solanafm/components/index.ts +++ b/src/content/solanafm/components/index.ts @@ -2,3 +2,4 @@ export { default as PhalconExplorerButton } from './PhalconExplorerButton' export { default as FundFlowButton } from './FundFlowButton' export { default as MainAddressLabel } from './MainAddressLabel' export { default as MainPrivateLabel } from './MainPrivateLabel' +export { default as ParsersButton } from './ParsersButton' diff --git a/src/content/solanafm/feat-scripts/index.ts b/src/content/solanafm/feat-scripts/index.ts index 2e2bc30..629a0a6 100644 --- a/src/content/solanafm/feat-scripts/index.ts +++ b/src/content/solanafm/feat-scripts/index.ts @@ -2,3 +2,5 @@ export { default as renderFundFlowButton } from './fund-flow' export { default as renderMainAddressLabel } from './main-address-label' export { default as renderEnhancedLabels } from './enhanced-labels' export { default as renderTxPageAddressLabels } from './tx-address-labels' +export { default as renderAlternativeParsers } from './quick2parsers' +export { default as renderTransactionHashPhalconLink } from './transaction-hash-phalcon-link' diff --git a/src/content/solanafm/feat-scripts/quick2parsers.tsx b/src/content/solanafm/feat-scripts/quick2parsers.tsx new file mode 100644 index 0000000..d4fba9e --- /dev/null +++ b/src/content/solanafm/feat-scripts/quick2parsers.tsx @@ -0,0 +1,17 @@ +import { createRoot } from 'react-dom/client' +import $ from 'jquery' + +import { ParsersButton } from '../components' + +const renderAlternativeParsers = async () => { + const container = $( + '#__next > main > div:nth-of-type(2) > div:nth-of-type(1) > div:nth-of-type(1) > div:nth-of-type(1) > div:nth-of-type(2) > div:nth-of-type(2) > div > div' + ) + const txHash = container.text().trim() + const rootEl = $('') + container.append(rootEl) + + createRoot(rootEl[0]).render() +} + +export default renderAlternativeParsers diff --git a/src/content/solanafm/feat-scripts/transaction-hash-phalcon-link.tsx b/src/content/solanafm/feat-scripts/transaction-hash-phalcon-link.tsx new file mode 100644 index 0000000..3fe8271 --- /dev/null +++ b/src/content/solanafm/feat-scripts/transaction-hash-phalcon-link.tsx @@ -0,0 +1,83 @@ +import React, { type FC, type ReactNode } from 'react' +import isMobile from 'is-mobile' +import { createRoot } from 'react-dom/client' + +import { PHALCON_SUPPORT_LIST } from '@common/constants' +import { getChainSimpleName } from '@common/utils' +import { PHALCON_EXPLORER_DOMAIN } from '@common/config/uri' +import { IconPhalcon } from '@common/components' + +const PhalconExplorerButton: FC<{ hash: string }> = ({ hash }) => { + const chain = getChainSimpleName() + + const pathname = PHALCON_SUPPORT_LIST.find( + item => item.chain === chain + )?.pathname + + const handleClick = (e: React.MouseEvent) => { + e.preventDefault() + window.open(`${PHALCON_EXPLORER_DOMAIN}/tx/${pathname}/${hash}`, '_blank') + } + + if (!chain) return null + return +} + +const appendIconToElement = (el: HTMLElement, reactNode: ReactNode) => { + if (!isMobile()) { + el.onmouseover = () => { + const btnEls = el.querySelectorAll( + '.__metadock-copy-address-btn__' + ) + if (btnEls.length) { + btnEls.forEach(btnEl => { + btnEl.style.display = 'inline-flex' + }) + } + } + el.onmouseout = () => { + const btnEls = el.querySelectorAll( + '.__metadock-copy-address-btn__' + ) + if (btnEls.length) { + btnEls.forEach(btnEl => { + btnEl.style.display = 'none' + }) + } + } + } + + el.setAttribute('style', 'padding-right:20px;position:relative;') + + const rootEl = document.createElement('span') + rootEl.classList.add('__metadock-copy-address-btn__') + rootEl.setAttribute( + 'style', + `position:absolute;right:0;display:${ + isMobile() ? 'inline-flex' : 'none' + };top: 50%;transform: translateY(-50%)` + ) + el?.appendChild(rootEl) + createRoot(rootEl).render(reactNode) +} + +const renderTransactionHashPhalconLink = async () => { + const txnTags = document.querySelectorAll( + "table a[href^='/tx/']" + ) + console.log('txnTags', txnTags) + for (let i = 0; i < txnTags.length; i++) { + const el = txnTags[i] + const href = el.getAttribute('href') + if (!href) continue + const txnHash = href.substring(href.lastIndexOf('/') + 1) + let targetEl: HTMLElement | null = null + if (!el.parentElement) return + targetEl = el.parentElement.parentElement + if (targetEl && txnHash) { + appendIconToElement(targetEl, ) + } + } +} + +export default renderTransactionHashPhalconLink diff --git a/src/content/solanafm/helper.ts b/src/content/solanafm/helper.ts index aa52175..3295802 100644 --- a/src/content/solanafm/helper.ts +++ b/src/content/solanafm/helper.ts @@ -5,7 +5,8 @@ export const lazyLoad = ( inspector: string, maxRetries = 60 ) => { - const loadingDocument = !$('#__next > main').length + const loadingDocument = + !$('#__next > main').length || !!$('[aria-label="toast-loading"]').length if (loadingDocument) { if (maxRetries > 0) { setTimeout(() => { @@ -14,7 +15,6 @@ export const lazyLoad = ( } } else { const loadingContent = !!$(inspector).length - if (loadingContent) { if (maxRetries > 0) { setTimeout(() => { diff --git a/src/content/solanafm/index.tsx b/src/content/solanafm/index.tsx index 2cf5cdc..0eaad3b 100644 --- a/src/content/solanafm/index.tsx +++ b/src/content/solanafm/index.tsx @@ -1,32 +1,15 @@ import browser from 'webextension-polyfill' import '@common/styles/inject.common' -import { getPageName, isHashItemsSimilar } from '@common/utils' import { URL_UPDATED } from '@common/constants' import execute from './main' export const initSolscanFM = async () => { execute() - let pageName = getPageName(window.location.hash.substring(1)) - let originURL = window.location.href browser.runtime.onMessage.addListener((message, _sender, sendResponse) => { if (message === URL_UPDATED) { - const newPageName = getPageName() - if (newPageName !== pageName) { - execute() - } else { - const isSimilar = isHashItemsSimilar( - new URL(originURL).hash, - window.location.hash, - 3 - ) - if (!isSimilar) { - execute() - } - } - originURL = window.location.href - pageName = newPageName + execute() sendResponse() } }) diff --git a/src/content/solanafm/page-scripts/address.tsx b/src/content/solanafm/page-scripts/address.tsx index 150d54a..38e5b15 100644 --- a/src/content/solanafm/page-scripts/address.tsx +++ b/src/content/solanafm/page-scripts/address.tsx @@ -7,28 +7,22 @@ import { GET_SOLANAFM_ACCOUNT_INFO, GET_SOLANAFM_ACCOUNT_TRANSFERS } from '@common/constants' +import { pickSolanaAddress } from '@common/utils' import { renderFundFlowButton, renderMainAddressLabel, - renderEnhancedLabels + renderEnhancedLabels, + renderTransactionHashPhalconLink } from '../feat-scripts' import { lazyLoad } from '../helper' const createAccountBox = () => { if ($('#md-account-box').length) return Promise.reject() - const isWalletAccount = - $( - '#__next > main > div:nth-of-type(2) > div:nth-of-type(1) > div > div > div:nth-of-type(1) > div > div > div > div > div:nth-of-type(1) > div > div > h1' - ) - .text() - .indexOf('Wallet Account') !== -1 const container = $( - isWalletAccount - ? '#__next > main > div:nth-of-type(2) > div:nth-of-type(1) > div > div > div:nth-of-type(1) > div > div:nth-of-type(1) > div > div > div:last-child > div' - : '#__next > main > div:nth-of-type(2) > div:nth-of-type(1) > div > div > div:nth-of-type(1) > div > div:nth-of-type(1) > div > div > div:last-child > div > div:nth-of-type(2) > div:nth-of-type(1)' - ) - const mainAddress = container.find('>span:first-child').text().trim() + '#__next > main > div:nth-of-type(2) > div:nth-of-type(1) > div > div > div:nth-of-type(1) div.text-ellipsis' + ).parent() + const mainAddress = pickSolanaAddress(window.location.href) const accountBox = $( `
` ) @@ -40,7 +34,20 @@ const createAccountBox = () => { } const initAddressPageScript = async () => { - const { fundFlow, enhancedLabels } = await store.get('options') + const { fundFlow, enhancedLabels, quick2Parsers } = await store.get('options') + + lazyLoad(() => { + if (quick2Parsers) { + renderTransactionHashPhalconLink() + $( + '#__next > main > div:nth-of-type(2) > div:nth-of-type(1) > div > div > div:nth-of-type(2) > div:nth-of-type(2) > div:nth-of-type(2) > div > div > div > div:nth-of-type(2)> div:nth-of-type(2) button' + ).click(function () { + setTimeout(() => { + renderTransactionHashPhalconLink() + }) + }) + } + }, '.animate-pulse') browser.runtime.onMessage.addListener((message, _sender, sendResponse) => { if (message === GET_SOLANAFM_ACCOUNT_INFO) { diff --git a/src/content/solanafm/page-scripts/tx.tsx b/src/content/solanafm/page-scripts/tx.tsx index 78a0a74..dcf9fed 100644 --- a/src/content/solanafm/page-scripts/tx.tsx +++ b/src/content/solanafm/page-scripts/tx.tsx @@ -1,12 +1,16 @@ import { store } from '@src/store' -import { renderTxPageAddressLabels } from '../feat-scripts' +import { + renderTxPageAddressLabels, + renderAlternativeParsers +} from '../feat-scripts' import { lazyLoad } from '../helper' const initTxPageScript = async () => { - const { enhancedLabels } = await store.get('options') + const { enhancedLabels, quick2Parsers } = await store.get('options') lazyLoad(() => { if (enhancedLabels) renderTxPageAddressLabels() + if (quick2Parsers) renderAlternativeParsers() }, '[data-testid="oval-loading"]') } diff --git a/src/content/solscan/components/ParsersButton/index.tsx b/src/content/solscan/components/ParsersButton/index.tsx new file mode 100644 index 0000000..5d5f3b1 --- /dev/null +++ b/src/content/solscan/components/ParsersButton/index.tsx @@ -0,0 +1,28 @@ +import React, { type FC } from 'react' + +import { PHALCON_EXPLORER_DOMAIN } from '@common/config/uri' +import { getChainSimpleName } from '@common/utils' +import { PHALCON_SUPPORT_LIST } from '@common/constants' +import { IconPhalcon } from '@common/components' + +interface Props { + txHash: string +} + +const ParsersBtn: FC = ({ txHash }) => { + const chain = getChainSimpleName() + + const pathname = PHALCON_SUPPORT_LIST.find( + item => item.chain === chain + )?.pathname + + const handleClick = (e: React.MouseEvent) => { + e.preventDefault() + window.open(`${PHALCON_EXPLORER_DOMAIN}/tx/${pathname}/${txHash}`, '_blank') + } + + if (!chain) return null + return +} + +export default ParsersBtn diff --git a/src/content/solscan/components/index.ts b/src/content/solscan/components/index.ts index 2915896..1b3141b 100644 --- a/src/content/solscan/components/index.ts +++ b/src/content/solscan/components/index.ts @@ -2,3 +2,4 @@ export { default as PhalconExplorerButton } from './PhalconExplorerButton' export { default as FundFlowButton } from './FundFlowButton' export { default as MainAddressLabel } from './MainAddressLabel' export { default as MainPrivateLabel } from './MainPrivateLabel' +export { default as ParsersButton } from './ParsersButton' diff --git a/src/content/solscan/feat-scripts/index.ts b/src/content/solscan/feat-scripts/index.ts index 2e2bc30..872672c 100644 --- a/src/content/solscan/feat-scripts/index.ts +++ b/src/content/solscan/feat-scripts/index.ts @@ -2,3 +2,5 @@ export { default as renderFundFlowButton } from './fund-flow' export { default as renderMainAddressLabel } from './main-address-label' export { default as renderEnhancedLabels } from './enhanced-labels' export { default as renderTxPageAddressLabels } from './tx-address-labels' +export { default as renderTransactionHashPhalconLink } from './transaction-hash-phalcon-link' +export { default as renderAlternativeParsers } from './quick2parsers' diff --git a/src/content/solscan/feat-scripts/quick2parsers.tsx b/src/content/solscan/feat-scripts/quick2parsers.tsx new file mode 100644 index 0000000..a6a0d37 --- /dev/null +++ b/src/content/solscan/feat-scripts/quick2parsers.tsx @@ -0,0 +1,20 @@ +import { createRoot } from 'react-dom/client' +import $ from 'jquery' + +import { ParsersButton } from '../components' + +const renderAlternativeParsers = async () => { + if ($('#__metadock-alternative-parsers__').length) return + const container = $( + '#__next > div:nth-of-type(1) > div:nth-of-type(3) > div:nth-of-type(1) > div:nth-of-type(2) > div > div > div:nth-of-type(2) > div > div > div:nth-of-type(1) > div:nth-of-type(2) > div:nth-of-type(1)' + ) + const txHash = container.text().trim() + const rootEl = $( + '' + ) + container.append(rootEl) + + createRoot(rootEl[0]).render() +} + +export default renderAlternativeParsers diff --git a/src/content/solscan/feat-scripts/transaction-hash-phalcon-link.tsx b/src/content/solscan/feat-scripts/transaction-hash-phalcon-link.tsx new file mode 100644 index 0000000..f66b016 --- /dev/null +++ b/src/content/solscan/feat-scripts/transaction-hash-phalcon-link.tsx @@ -0,0 +1,85 @@ +import React, { type FC, type ReactNode } from 'react' +import isMobile from 'is-mobile' +import { createRoot } from 'react-dom/client' + +import { PHALCON_SUPPORT_LIST } from '@common/constants' +import { getChainSimpleName } from '@common/utils' +import { PHALCON_EXPLORER_DOMAIN } from '@common/config/uri' +import { IconPhalcon } from '@common/components' + +const PhalconExplorerButton: FC<{ hash: string }> = ({ hash }) => { + const chain = getChainSimpleName() + + const pathname = PHALCON_SUPPORT_LIST.find( + item => item.chain === chain + )?.pathname + + const handleClick = (e: React.MouseEvent) => { + e.preventDefault() + window.open(`${PHALCON_EXPLORER_DOMAIN}/tx/${pathname}/${hash}`, '_blank') + } + + if (!chain) return null + return +} + +const appendIconToElement = (el: HTMLElement, reactNode: ReactNode) => { + if (!isMobile()) { + el.onmouseover = () => { + const btnEls = el.querySelectorAll( + '.__metadock-copy-address-btn__' + ) + if (btnEls.length) { + btnEls.forEach(btnEl => { + btnEl.style.display = 'inline-flex' + }) + } + } + el.onmouseout = () => { + const btnEls = el.querySelectorAll( + '.__metadock-copy-address-btn__' + ) + if (btnEls.length) { + btnEls.forEach(btnEl => { + btnEl.style.display = 'none' + }) + } + } + } + + el.setAttribute( + 'style', + 'padding-right:20px;position:relative;width:fit-content;' + ) + + const rootEl = document.createElement('span') + rootEl.classList.add('__metadock-copy-address-btn__') + rootEl.setAttribute( + 'style', + `position:absolute;right:0;display:${ + isMobile() ? 'inline-flex' : 'none' + };top: 50%;transform: translateY(-50%)` + ) + el?.appendChild(rootEl) + createRoot(rootEl).render(reactNode) +} + +const renderTransactionHashPhalconLink = async () => { + const txnTags = document.querySelectorAll( + "table a[href^='/tx/']" + ) + for (let i = 0; i < txnTags.length; i++) { + const el = txnTags[i] + const href = el.getAttribute('href') + if (!href) continue + const txnHash = href.substring(href.lastIndexOf('/') + 1) + let targetEl: HTMLElement | null = null + if (!el.parentElement) return + targetEl = el.parentElement.parentElement + if (targetEl && txnHash) { + appendIconToElement(targetEl, ) + } + } +} + +export default renderTransactionHashPhalconLink diff --git a/src/content/solscan/helper.ts b/src/content/solscan/helper.ts index 45b6a76..70d6597 100644 --- a/src/content/solscan/helper.ts +++ b/src/content/solscan/helper.ts @@ -3,13 +3,14 @@ import $ from 'jquery' export const lazyLoad = ( callback: () => void, inspector: string, + isNegative = true, maxRetries = 60 ) => { - const loading = !$(inspector).length + const loading = isNegative ? !$(inspector).length : !!$(inspector).length if (loading) { if (maxRetries > 0) { setTimeout(() => { - lazyLoad(callback, inspector, maxRetries - 1) + lazyLoad(callback, inspector, isNegative, maxRetries - 1) }, 500) } } else { diff --git a/src/content/solscan/main.tsx b/src/content/solscan/main.tsx index 5d78ffe..ddfcacc 100644 --- a/src/content/solscan/main.tsx +++ b/src/content/solscan/main.tsx @@ -6,7 +6,8 @@ import { SOLSCAN_PAGES } from '@common/constants' import { initAccountPageScript, initTxPageScript, - initTokenPageScript + initTokenPageScript, + initBlockPageScript } from './page-scripts' const execute = async () => { @@ -28,6 +29,9 @@ const execute = async () => { case SOLSCAN_PAGES.TX.name: initTxPageScript() break + case SOLSCAN_PAGES.BLOCK.name: + initBlockPageScript() + break } } diff --git a/src/content/solscan/page-scripts/account.tsx b/src/content/solscan/page-scripts/account.tsx index 5c9a7d3..9c97738 100644 --- a/src/content/solscan/page-scripts/account.tsx +++ b/src/content/solscan/page-scripts/account.tsx @@ -6,12 +6,13 @@ import { GET_SOLSCAN_ACCOUNT_TAB_DATA, SOLSCAN_PAGES } from '@common/constants' import { renderFundFlowButton, renderMainAddressLabel, - renderEnhancedLabels + renderEnhancedLabels, + renderTransactionHashPhalconLink } from '../feat-scripts' import { lazyLoad } from '../helper' const initAccountPageScript = async () => { - const { fundFlow, enhancedLabels } = await store.get('options') + const { fundFlow, enhancedLabels, quick2Parsers } = await store.get('options') lazyLoad(() => { if (fundFlow) renderFundFlowButton(SOLSCAN_PAGES.ACCOUNT.name) @@ -21,10 +22,19 @@ const initAccountPageScript = async () => { } }, '#account-tabs') + lazyLoad( + () => { + if (quick2Parsers) renderTransactionHashPhalconLink() + }, + '.animate-pulse', + false + ) + browser.runtime.onMessage.addListener((message, _sender, sendResponse) => { if (message === GET_SOLSCAN_ACCOUNT_TAB_DATA) { requestIdleCallback(() => { if (enhancedLabels) renderEnhancedLabels() + if (quick2Parsers) renderTransactionHashPhalconLink() }) sendResponse() } diff --git a/src/content/solscan/page-scripts/block.tsx b/src/content/solscan/page-scripts/block.tsx new file mode 100644 index 0000000..07788fd --- /dev/null +++ b/src/content/solscan/page-scripts/block.tsx @@ -0,0 +1,34 @@ +import browser from 'webextension-polyfill' + +import { store } from '@src/store' +import { GET_SOLSCAN_BLOCK_TXS } from '@common/constants' + +import { renderTransactionHashPhalconLink } from '../feat-scripts' +import { lazyLoad } from '../helper' + +const initBlockPageScript = async () => { + const { quick2Parsers } = await store.get('options') + + lazyLoad( + () => { + if (quick2Parsers) renderTransactionHashPhalconLink() + }, + '.animate-pulse', + false + ) + + browser.runtime.onMessage.addListener((message, _sender, sendResponse) => { + if (message === GET_SOLSCAN_BLOCK_TXS) { + lazyLoad( + () => { + if (quick2Parsers) renderTransactionHashPhalconLink() + }, + '.animate-pulse', + false + ) + sendResponse() + } + }) +} + +export default initBlockPageScript diff --git a/src/content/solscan/page-scripts/index.ts b/src/content/solscan/page-scripts/index.ts index 30d8686..97ed787 100644 --- a/src/content/solscan/page-scripts/index.ts +++ b/src/content/solscan/page-scripts/index.ts @@ -1,3 +1,4 @@ export { default as initAccountPageScript } from './account' export { default as initTxPageScript } from './tx' export { default as initTokenPageScript } from './token' +export { default as initBlockPageScript } from './block' diff --git a/src/content/solscan/page-scripts/tx.tsx b/src/content/solscan/page-scripts/tx.tsx index 283b157..331558e 100644 --- a/src/content/solscan/page-scripts/tx.tsx +++ b/src/content/solscan/page-scripts/tx.tsx @@ -3,16 +3,25 @@ import browser from 'webextension-polyfill' import { store } from '@src/store' import { GET_SOLSCAN_TRANSACTION } from '@common/constants' -import { renderTxPageAddressLabels } from '../feat-scripts' +import { + renderTxPageAddressLabels, + renderAlternativeParsers +} from '../feat-scripts' +import { lazyLoad } from '../helper' const initTxPageScript = async () => { - const { enhancedLabels } = await store.get('options') + const { enhancedLabels, quick2Parsers } = await store.get('options') browser.runtime.onMessage.addListener((message, _sender, sendResponse) => { if (message === GET_SOLSCAN_TRANSACTION) { - requestIdleCallback(() => { - if (enhancedLabels) renderTxPageAddressLabels() - }) + lazyLoad( + () => { + if (enhancedLabels) renderTxPageAddressLabels() + if (quick2Parsers) renderAlternativeParsers() + }, + '.animate-pulse', + false + ) sendResponse() } })