Show raw tx hex in UI (#1162)

* Fix Network configuration parameter

* feat: allow for showing raw transaction hex for ETH transactions

* chore: remove comments from JS code to avoid parsing issues in tests

* temp: comment out failing tx template tests

* chore: trim text from copyable before writing it to clipboard

* chore: improve the design of Transaction hex

* chore: add wrap to element showing raw hex data

* fixup! chore: add wrap to element showing raw hex data

* chore: remove redundant style, make HTML prettier

* Revert "temp: comment out failing tx template tests"

This reverts commit f104ebbf5111583b46996d7527a26c08cd9e29b6.

* chore: put rawTx javascript functionality into main.js

* chore: modify the expected HTML for changed tx template

* feat: support the raw transaction hex also for BTC-like coins

* chore: add on-hover effect for active button - make the background white

* Minify javascript and styles

---------

Co-authored-by: Martin Boehm <martin.boehm@1mbsoftware.net>
This commit is contained in:
Jiří Musil
2024-12-09 11:30:02 +01:00
committed by GitHub
parent e283ac8915
commit a4f1730364
24 changed files with 305 additions and 169 deletions

View File

@@ -207,6 +207,11 @@ func (w *Worker) GetTransaction(txid string, spendingTxs bool, specificJSON bool
return tx, nil
}
// GetRawTransaction gets raw transaction data in hex format from txid
func (w *Worker) GetRawTransaction(txid string) (string, error) {
return w.chain.EthereumTypeGetRawTransaction(txid)
}
// getTransaction reads transaction data from txid
func (w *Worker) getTransaction(txid string, spendingTxs bool, specificJSON bool, addresses map[string]struct{}) (*Tx, error) {
bchainTx, height, err := w.txCache.GetTransaction(txid)

View File

@@ -81,3 +81,7 @@ func (b *BaseChain) EthereumTypeGetStakingPoolsData(addrDesc AddressDescriptor)
func (b *BaseChain) EthereumTypeRpcCall(data, to, from string) (string, error) {
return "", errors.New("not supported")
}
func (b *BaseChain) EthereumTypeGetRawTransaction(txid string) (string, error) {
return "", errors.New("not supported")
}

View File

@@ -354,6 +354,11 @@ func (c *blockChainWithMetrics) EthereumTypeRpcCall(data, to, from string) (v st
return c.b.EthereumTypeRpcCall(data, to, from)
}
func (c *blockChainWithMetrics) EthereumTypeGetRawTransaction(txid string) (v string, err error) {
defer func(s time.Time) { c.observeRPCLatency("EthereumTypeGetRawTransaction", s, err) }(time.Now())
return c.b.EthereumTypeGetRawTransaction(txid)
}
type mempoolWithMetrics struct {
mempool bchain.Mempool
m *common.Metrics

View File

@@ -40,6 +40,7 @@ const (
type Configuration struct {
CoinName string `json:"coin_name"`
CoinShortcut string `json:"coin_shortcut"`
Network string `json:"network"`
RPCURL string `json:"rpc_url"`
RPCTimeout int `json:"rpc_timeout"`
BlockAddressesToKeep int `json:"block_addresses_to_keep"`
@@ -159,7 +160,12 @@ func (b *EthereumRPC) Initialize() error {
return errors.Errorf("Unknown network id %v", id)
}
err = b.initStakingPools(b.ChainConfig.CoinShortcut)
networkConfig := b.ChainConfig.Network
if networkConfig == "" {
networkConfig = b.ChainConfig.CoinShortcut
}
err = b.initStakingPools(networkConfig)
if err != nil {
return err
}
@@ -988,21 +994,31 @@ func (b *EthereumRPC) EthereumTypeEstimateGas(params map[string]interface{}) (ui
// SendRawTransaction sends raw transaction
func (b *EthereumRPC) SendRawTransaction(hex string) (string, error) {
return b.callRpcStringResult("eth_sendRawTransaction", hex)
}
// EthereumTypeGetRawTransaction gets raw transaction in hex format
func (b *EthereumRPC) EthereumTypeGetRawTransaction(txid string) (string, error) {
return b.callRpcStringResult("eth_getRawTransactionByHash", txid)
}
// Helper function for calling ETH RPC with parameters and getting string result
func (b *EthereumRPC) callRpcStringResult(rpcMethod string, args ...interface{}) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
defer cancel()
var raw json.RawMessage
err := b.RPC.CallContext(ctx, &raw, "eth_sendRawTransaction", hex)
err := b.RPC.CallContext(ctx, &raw, rpcMethod, args...)
if err != nil {
return "", err
} else if len(raw) == 0 {
return "", errors.New("SendRawTransaction: failed")
return "", errors.New(rpcMethod + " : failed")
}
var result string
if err := json.Unmarshal(raw, &result); err != nil {
return "", errors.Annotatef(err, "raw result %v", raw)
}
if result == "" {
return "", errors.New("SendRawTransaction: failed, empty result")
return "", errors.New(rpcMethod + " : failed, empty result")
}
return result, nil
}

View File

@@ -11,9 +11,9 @@ import (
"github.com/trezor/blockbook/bchain"
)
func (b *EthereumRPC) initStakingPools(coinShortcut string) error {
func (b *EthereumRPC) initStakingPools(network string) error {
// for now only single staking pool
envVar := strings.ToUpper(coinShortcut) + "_STAKING_POOL_CONTRACT"
envVar := strings.ToUpper(network) + "_STAKING_POOL_CONTRACT"
envValue := os.Getenv(envVar)
if envValue != "" {
parts := strings.Split(envValue, "/")

View File

@@ -337,6 +337,7 @@ type BlockChain interface {
EthereumTypeGetSupportedStakingPools() []string
EthereumTypeGetStakingPoolsData(addrDesc AddressDescriptor) ([]StakingPoolData, error)
EthereumTypeRpcCall(data, to, from string) (string, error)
EthereumTypeGetRawTransaction(txid string) (string, error)
GetTokenURI(contractDesc AddressDescriptor, tokenID *big.Int) (string, error)
}

View File

@@ -507,7 +507,7 @@ func newInternalState(config *common.Config, d *db.RocksDB, enableSubNewTx bool)
is.Host = name
}
is.WsGetAccountInfoLimit, _ = strconv.Atoi(os.Getenv(strings.ToUpper(is.CoinShortcut) + "_WS_GETACCOUNTINFO_LIMIT"))
is.WsGetAccountInfoLimit, _ = strconv.Atoi(os.Getenv(strings.ToUpper(is.GetNetwork()) + "_WS_GETACCOUNTINFO_LIMIT"))
if is.WsGetAccountInfoLimit > 0 {
glog.Info("WsGetAccountInfoLimit enabled with limit ", is.WsGetAccountInfoLimit)
is.WsLimitExceedingIPs = make(map[string]int)

View File

@@ -1,65 +1,66 @@
{
"coin": {
"name": "Arbitrum",
"shortcut": "ETH",
"label": "Arbitrum",
"alias": "arbitrum"
},
"ports": {
"backend_rpc": 8205,
"backend_p2p": 38405,
"backend_http": 8305,
"blockbook_internal": 9205,
"blockbook_public": 9305
},
"ipc": {
"rpc_url_template": "ws://127.0.0.1:{{.Ports.BackendRPC}}",
"rpc_timeout": 25
},
"backend": {
"package_name": "backend-arbitrum",
"package_revision": "satoshilabs-1",
"system_user": "arbitrum",
"version": "3.2.1",
"docker_image": "offchainlabs/nitro-node:v3.2.1-d81324d",
"verification_type": "docker",
"verification_source": "724ebdcca39cd0c28ffd025ecea8d1622a376f41344201b729afb60352cbc306",
"extract_command": "docker cp extract:/home/user/target backend/target; docker cp extract:/home/user/nitro-legacy backend/nitro-legacy; docker cp extract:/usr/local/bin/nitro backend/nitro",
"exclude_files": [],
"exec_command_template": "/bin/sh -c '{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/arbitrum_exec.sh 2>> {{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/{{.Coin.Alias}}.log'",
"exec_script": "arbitrum.sh",
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/{{.Coin.Alias}}.log",
"postinst_script_template": "openssl rand -hex 32 > {{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/jwtsecret",
"service_type": "simple",
"service_additional_params_template": "",
"protect_memory": true,
"mainnet": true,
"server_config_file": "",
"client_config_file": ""
},
"blockbook": {
"package_name": "blockbook-arbitrum",
"system_user": "blockbook-arbitrum",
"internal_binding_template": ":{{.Ports.BlockbookInternal}}",
"public_binding_template": ":{{.Ports.BlockbookPublic}}",
"explorer_url": "",
"additional_params": "",
"block_chain": {
"parse": true,
"mempool_workers": 8,
"mempool_sub_workers": 2,
"block_addresses_to_keep": 300,
"additional_params": {
"mempoolTxTimeoutHours": 48,
"queryBackendOnMempoolResync": false,
"fiat_rates": "coingecko",
"fiat_rates_vs_currencies": "AED,ARS,AUD,BDT,BHD,BMD,BRL,CAD,CHF,CLP,CNY,CZK,DKK,EUR,GBP,HKD,HUF,IDR,ILS,INR,JPY,KRW,KWD,LKR,MMK,MXN,MYR,NGN,NOK,NZD,PHP,PKR,PLN,RUB,SAR,SEK,SGD,THB,TRY,TWD,UAH,USD,VEF,VND,ZAR,BTC,ETH",
"fiat_rates_params": "{\"url\": \"https://api.coingecko.com/api/v3\", \"coin\": \"ethereum\",\"platformIdentifier\": \"ethereum\",\"platformVsCurrency\": \"eth\",\"periodSeconds\": 900}"
}
"coin": {
"name": "Arbitrum",
"shortcut": "ETH",
"network": "ARB",
"label": "Arbitrum",
"alias": "arbitrum"
},
"ports": {
"backend_rpc": 8205,
"backend_p2p": 38405,
"backend_http": 8305,
"blockbook_internal": 9205,
"blockbook_public": 9305
},
"ipc": {
"rpc_url_template": "ws://127.0.0.1:{{.Ports.BackendRPC}}",
"rpc_timeout": 25
},
"backend": {
"package_name": "backend-arbitrum",
"package_revision": "satoshilabs-1",
"system_user": "arbitrum",
"version": "3.2.1",
"docker_image": "offchainlabs/nitro-node:v3.2.1-d81324d",
"verification_type": "docker",
"verification_source": "724ebdcca39cd0c28ffd025ecea8d1622a376f41344201b729afb60352cbc306",
"extract_command": "docker cp extract:/home/user/target backend/target; docker cp extract:/home/user/nitro-legacy backend/nitro-legacy; docker cp extract:/usr/local/bin/nitro backend/nitro",
"exclude_files": [],
"exec_command_template": "/bin/sh -c '{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/arbitrum_exec.sh 2>> {{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/{{.Coin.Alias}}.log'",
"exec_script": "arbitrum.sh",
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/{{.Coin.Alias}}.log",
"postinst_script_template": "openssl rand -hex 32 > {{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/jwtsecret",
"service_type": "simple",
"service_additional_params_template": "",
"protect_memory": true,
"mainnet": true,
"server_config_file": "",
"client_config_file": ""
},
"blockbook": {
"package_name": "blockbook-arbitrum",
"system_user": "blockbook-arbitrum",
"internal_binding_template": ":{{.Ports.BlockbookInternal}}",
"public_binding_template": ":{{.Ports.BlockbookPublic}}",
"explorer_url": "",
"additional_params": "",
"block_chain": {
"parse": true,
"mempool_workers": 8,
"mempool_sub_workers": 2,
"block_addresses_to_keep": 300,
"additional_params": {
"mempoolTxTimeoutHours": 48,
"queryBackendOnMempoolResync": false,
"fiat_rates": "coingecko",
"fiat_rates_vs_currencies": "AED,ARS,AUD,BDT,BHD,BMD,BRL,CAD,CHF,CLP,CNY,CZK,DKK,EUR,GBP,HKD,HUF,IDR,ILS,INR,JPY,KRW,KWD,LKR,MMK,MXN,MYR,NGN,NOK,NZD,PHP,PKR,PLN,RUB,SAR,SEK,SGD,THB,TRY,TWD,UAH,USD,VEF,VND,ZAR,BTC,ETH",
"fiat_rates_params": "{\"url\": \"https://api.coingecko.com/api/v3\", \"coin\": \"ethereum\",\"platformIdentifier\": \"ethereum\",\"platformVsCurrency\": \"eth\",\"periodSeconds\": 900}"
}
}
},
"meta": {
"package_maintainer": "IT",
"package_maintainer_email": "it@satoshilabs.com"
}
},
"meta": {
"package_maintainer": "IT",
"package_maintainer_email": "it@satoshilabs.com"
}
}

View File

@@ -1,67 +1,68 @@
{
"coin": {
"name": "Arbitrum Archive",
"shortcut": "ETH",
"label": "Arbitrum",
"alias": "arbitrum_archive"
},
"ports": {
"backend_rpc": 8306,
"backend_p2p": 38406,
"blockbook_internal": 9206,
"blockbook_public": 9306
},
"ipc": {
"rpc_url_template": "ws://127.0.0.1:{{.Ports.BackendRPC}}",
"rpc_timeout": 25
},
"backend": {
"package_name": "backend-arbitrum-archive",
"package_revision": "satoshilabs-1",
"system_user": "arbitrum",
"version": "3.2.1",
"docker_image": "offchainlabs/nitro-node:v3.2.1-d81324d",
"verification_type": "docker",
"verification_source": "724ebdcca39cd0c28ffd025ecea8d1622a376f41344201b729afb60352cbc306",
"extract_command": "docker cp extract:/home/user/target backend/target; docker cp extract:/home/user/nitro-legacy backend/nitro-legacy; docker cp extract:/usr/local/bin/nitro backend/nitro",
"exclude_files": [],
"exec_command_template": "/bin/sh -c '{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/arbitrum_archive_exec.sh 2>> {{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/{{.Coin.Alias}}.log'",
"exec_script": "arbitrum_archive.sh",
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/{{.Coin.Alias}}.log",
"postinst_script_template": "openssl rand -hex 32 > {{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/jwtsecret",
"service_type": "simple",
"service_additional_params_template": "",
"protect_memory": true,
"mainnet": true,
"server_config_file": "",
"client_config_file": ""
},
"blockbook": {
"package_name": "blockbook-arbitrum-archive",
"system_user": "blockbook-arbitrum",
"internal_binding_template": ":{{.Ports.BlockbookInternal}}",
"public_binding_template": ":{{.Ports.BlockbookPublic}}",
"explorer_url": "",
"additional_params": "-workers=16",
"block_chain": {
"parse": true,
"mempool_workers": 8,
"mempool_sub_workers": 2,
"block_addresses_to_keep": 600,
"additional_params": {
"address_aliases": true,
"mempoolTxTimeoutHours": 48,
"processInternalTransactions": true,
"queryBackendOnMempoolResync": false,
"fiat_rates": "coingecko",
"fiat_rates_vs_currencies": "AED,ARS,AUD,BDT,BHD,BMD,BRL,CAD,CHF,CLP,CNY,CZK,DKK,EUR,GBP,HKD,HUF,IDR,ILS,INR,JPY,KRW,KWD,LKR,MMK,MXN,MYR,NGN,NOK,NZD,PHP,PKR,PLN,RUB,SAR,SEK,SGD,THB,TRY,TWD,UAH,USD,VEF,VND,ZAR,BTC,ETH",
"fiat_rates_params": "{\"url\": \"https://api.coingecko.com/api/v3\", \"coin\": \"ethereum\",\"platformIdentifier\": \"ethereum\",\"platformVsCurrency\": \"eth\",\"periodSeconds\": 900}",
"fourByteSignatures": "https://www.4byte.directory/api/v1/signatures/"
}
"coin": {
"name": "Arbitrum Archive",
"shortcut": "ETH",
"network": "ARB",
"label": "Arbitrum",
"alias": "arbitrum_archive"
},
"ports": {
"backend_rpc": 8306,
"backend_p2p": 38406,
"blockbook_internal": 9206,
"blockbook_public": 9306
},
"ipc": {
"rpc_url_template": "ws://127.0.0.1:{{.Ports.BackendRPC}}",
"rpc_timeout": 25
},
"backend": {
"package_name": "backend-arbitrum-archive",
"package_revision": "satoshilabs-1",
"system_user": "arbitrum",
"version": "3.2.1",
"docker_image": "offchainlabs/nitro-node:v3.2.1-d81324d",
"verification_type": "docker",
"verification_source": "724ebdcca39cd0c28ffd025ecea8d1622a376f41344201b729afb60352cbc306",
"extract_command": "docker cp extract:/home/user/target backend/target; docker cp extract:/home/user/nitro-legacy backend/nitro-legacy; docker cp extract:/usr/local/bin/nitro backend/nitro",
"exclude_files": [],
"exec_command_template": "/bin/sh -c '{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/arbitrum_archive_exec.sh 2>> {{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/{{.Coin.Alias}}.log'",
"exec_script": "arbitrum_archive.sh",
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/{{.Coin.Alias}}.log",
"postinst_script_template": "openssl rand -hex 32 > {{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/jwtsecret",
"service_type": "simple",
"service_additional_params_template": "",
"protect_memory": true,
"mainnet": true,
"server_config_file": "",
"client_config_file": ""
},
"blockbook": {
"package_name": "blockbook-arbitrum-archive",
"system_user": "blockbook-arbitrum",
"internal_binding_template": ":{{.Ports.BlockbookInternal}}",
"public_binding_template": ":{{.Ports.BlockbookPublic}}",
"explorer_url": "",
"additional_params": "-workers=16",
"block_chain": {
"parse": true,
"mempool_workers": 8,
"mempool_sub_workers": 2,
"block_addresses_to_keep": 600,
"additional_params": {
"address_aliases": true,
"mempoolTxTimeoutHours": 48,
"processInternalTransactions": true,
"queryBackendOnMempoolResync": false,
"fiat_rates": "coingecko",
"fiat_rates_vs_currencies": "AED,ARS,AUD,BDT,BHD,BMD,BRL,CAD,CHF,CLP,CNY,CZK,DKK,EUR,GBP,HKD,HUF,IDR,ILS,INR,JPY,KRW,KWD,LKR,MMK,MXN,MYR,NGN,NOK,NZD,PHP,PKR,PLN,RUB,SAR,SEK,SGD,THB,TRY,TWD,UAH,USD,VEF,VND,ZAR,BTC,ETH",
"fiat_rates_params": "{\"url\": \"https://api.coingecko.com/api/v3\", \"coin\": \"ethereum\",\"platformIdentifier\": \"ethereum\",\"platformVsCurrency\": \"eth\",\"periodSeconds\": 900}",
"fourByteSignatures": "https://www.4byte.directory/api/v1/signatures/"
}
}
},
"meta": {
"package_maintainer": "IT",
"package_maintainer_email": "it@satoshilabs.com"
}
},
"meta": {
"package_maintainer": "IT",
"package_maintainer_email": "it@satoshilabs.com"
}
}

View File

@@ -2,7 +2,7 @@
"coin": {
"name": "BNB Smart Chain",
"shortcut": "BNB",
"network": "BNB",
"network": "BSC",
"label": "BNB Smart Chain",
"alias": "bsc"
},

View File

@@ -2,7 +2,7 @@
"coin": {
"name": "BNB Smart Chain Archive",
"shortcut": "BNB",
"network": "BNB",
"network": "BSC",
"label": "BNB Smart Chain",
"alias": "bsc_archive"
},

View File

@@ -59,7 +59,7 @@ type marketChartPrices struct {
}
// NewCoinGeckoDownloader creates a coingecko structure that implements the RatesDownloaderInterface
func NewCoinGeckoDownloader(db *db.RocksDB, coinShortcut string, url string, coin string, platformIdentifier string, platformVsCurrency string, allowedVsCurrencies string, timeFormat string, metrics *common.Metrics, throttleDown bool) RatesDownloaderInterface {
func NewCoinGeckoDownloader(db *db.RocksDB, network string, url string, coin string, platformIdentifier string, platformVsCurrency string, allowedVsCurrencies string, timeFormat string, metrics *common.Metrics, throttleDown bool) RatesDownloaderInterface {
throttlingDelayMs := 0 // No delay by default
if throttleDown {
throttlingDelayMs = DefaultThrottleDelayMs
@@ -67,7 +67,7 @@ func NewCoinGeckoDownloader(db *db.RocksDB, coinShortcut string, url string, coi
allowedVsCurrenciesMap := getAllowedVsCurrenciesMap(allowedVsCurrencies)
apiKey := os.Getenv(strings.ToUpper(coinShortcut) + "_COINGECKO_API_KEY")
apiKey := os.Getenv(strings.ToUpper(network) + "_COINGECKO_API_KEY")
if apiKey == "" {
apiKey = os.Getenv("COINGECKO_API_KEY")
}

View File

@@ -186,6 +186,7 @@ func (s *PublicServer) ConnectFullPublicInterface() {
serveMux.HandleFunc(path+"api/block-filters/", s.jsonHandler(s.apiBlockFilters, apiDefault))
serveMux.HandleFunc(path+"api/tx-specific/", s.jsonHandler(s.apiTxSpecific, apiDefault))
serveMux.HandleFunc(path+"api/tx/", s.jsonHandler(s.apiTx, apiDefault))
serveMux.HandleFunc(path+"api/rawtx/", s.jsonHandler(s.apiRawTx, apiDefault))
serveMux.HandleFunc(path+"api/address/", s.jsonHandler(s.apiAddress, apiDefault))
serveMux.HandleFunc(path+"api/xpub/", s.jsonHandler(s.apiXpub, apiDefault))
serveMux.HandleFunc(path+"api/utxo/", s.jsonHandler(s.apiUtxo, apiDefault))
@@ -303,7 +304,7 @@ func (s *PublicServer) newTemplateData(r *http.Request) *TemplateData {
t.MultiTokenName = bchain.EthereumTokenTypeMap[bchain.MultiToken]
}
if !s.debug {
t.Minified = ".min.3"
t.Minified = ".min.4"
}
if s.is.HasFiatRates {
// get the secondary coin and if it should be shown either from query parameters "secondary" and "use_secondary"
@@ -1288,6 +1289,19 @@ func (s *PublicServer) apiTx(r *http.Request, apiVersion int) (interface{}, erro
return tx, err
}
func (s *PublicServer) apiRawTx(r *http.Request, apiVersion int) (interface{}, error) {
var txid string
i := strings.LastIndexByte(r.URL.Path, '/')
if i > 0 {
txid = r.URL.Path[i+1:]
}
if len(txid) == 0 {
return "", api.NewAPIError("Missing txid", true)
}
s.metrics.ExplorerViews.With(common.Labels{"action": "api-raw-tx"}).Inc()
return s.api.GetRawTransaction(txid)
}
func (s *PublicServer) apiTxSpecific(r *http.Request, apiVersion int) (interface{}, error) {
var txid string
i := strings.LastIndexByte(r.URL.Path, '/')

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -107,7 +107,7 @@ func NewWebsocketServer(db *db.RocksDB, chain bchain.BlockChain, mempool bchain.
fiatRatesSubscriptions: make(map[string]map[*websocketChannel]string),
fiatRatesTokenSubscriptions: make(map[*websocketChannel][]string),
}
envRpcCall := os.Getenv(strings.ToUpper(is.CoinShortcut) + "_ALLOWED_RPC_CALL_TO")
envRpcCall := os.Getenv(strings.ToUpper(is.GetNetwork()) + "_ALLOWED_RPC_CALL_TO")
if envRpcCall != "" {
s.allowedRpcCallTo = make(map[string]struct{})
for _, c := range strings.Split(envRpcCall, ",") {

View File

@@ -200,6 +200,10 @@ span.btn-paging:hover {
color: #757575;
}
.btn-paging.active:hover {
background-color: white;
}
.paging-group {
border: 1px solid #e2e2e2;
border-radius: 0.5rem;
@@ -694,4 +698,4 @@ span.btn-paging:hover {
.btn {
--bs-btn-font-size: 1rem;
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -85,6 +85,79 @@ function addressAliasTooltip() {
return `<span class="l-tooltip">${type}<br>${address}</span>`;
}
function handleTxPage(rawData, txId) {
const rawOutput = document.getElementById('raw');
const rawButton = document.getElementById('raw-button');
const rawHexButton = document.getElementById('raw-hex-button');
rawOutput.innerHTML = syntaxHighlight(rawData);
let isShowingHexData = false;
const memoizedResponses = {};
async function getTransactionHex(txId) {
// BTC-like coins have a 'hex' field in the raw data
if (rawData['hex']) {
return rawData['hex'];
}
if (memoizedResponses[txId]) {
return memoizedResponses[txId];
}
const fetchedData = await fetchTransactionHex(txId);
memoizedResponses[txId] = fetchedData;
return fetchedData;
}
async function fetchTransactionHex(txId) {
const response = await fetch(`/api/rawtx/${txId}`);
if (!response.ok) {
throw new Error(`Error fetching data: ${response.status}`);
}
const txHex = await response.text();
const hexWithoutQuotes = txHex.replace(/"/g, '');
return hexWithoutQuotes;
}
function updateButtonStyles() {
if (isShowingHexData) {
rawButton.classList.add('active');
rawButton.style.fontWeight = 'normal';
rawHexButton.classList.remove('active');
rawHexButton.style.fontWeight = 'bold';
} else {
rawButton.classList.remove('active');
rawButton.style.fontWeight = 'bold';
rawHexButton.classList.add('active');
rawHexButton.style.fontWeight = 'normal';
}
}
updateButtonStyles();
rawHexButton.addEventListener('click', async () => {
if (!isShowingHexData) {
try {
const txHex = await getTransactionHex(txId);
rawOutput.textContent = txHex;
} catch (error) {
console.error('Error fetching raw transaction hex:', error);
rawOutput.textContent = `Error fetching raw transaction hex: ${error.message}`;
}
isShowingHexData = true;
updateButtonStyles();
}
});
rawButton.addEventListener('click', () => {
if (isShowingHexData) {
rawOutput.innerHTML = syntaxHighlight(rawData);
isShowingHexData = false;
updateButtonStyles();
}
});
}
window.addEventListener("DOMContentLoaded", () => {
const a = getCoinCookie();
if (a?.length === 3) {
@@ -127,7 +200,8 @@ window.addEventListener("DOMContentLoaded", () => {
if (e.clientX < e.target.getBoundingClientRect().x) {
let t = e.target.getAttribute("cc");
if (!t) t = e.target.innerText;
navigator.clipboard.writeText(t);
const textToCopy = t.trim();
navigator.clipboard.writeText(textToCopy);
e.target.className = e.target.className.replace("copyable", "copied");
setTimeout(
() =>

View File

@@ -1 +0,0 @@
function syntaxHighlight(t){return(t=(t=JSON.stringify(t,void 0,2)).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")).length>1e6?`<span class="key">${t}</span>`:t.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,t=>{let e="number";return/^"/.test(t)?e=/:$/.test(t)?"key":"string":/true|false/.test(t)?e="boolean":/null/.test(t)&&(e="null"),`<span class="${e}">${t}</span>`})}function getCoinCookie(){if(hasSecondary)return document.cookie.split("; ").find(t=>t.startsWith("secondary_coin="))?.split("=")}function changeCSSStyle(t,e,l){let a=document.all?"rules":"cssRules";for(i=0,len=document.styleSheets[1][a].length;i<len;i++)if(document.styleSheets[1][a][i].selectorText===t){document.styleSheets[1][a][i].style[e]=l;return}}function amountTooltip(){let t=this.querySelector(".prim-amt"),e=this.querySelector(".sec-amt"),l=this.querySelector(".csec-amt"),a=this.querySelector(".base-amt"),s=this.querySelector(".cbase-amt"),r=`${t.outerHTML}<br>`;if(a){let n=a.getAttribute("tm");n||(n="now"),r+=`<span class="amt-time">${n}</span>${a.outerHTML}<br>`}if(s&&(r+=`<span class="amt-time">now</span>${s.outerHTML}<br>`),e){let o=e.getAttribute("tm");o||(o="now"),r+=`<span class="amt-time">${o}</span>${e.outerHTML}<br>`}return l&&(r+=`<span class="amt-time">now</span>${l.outerHTML}<br>`),`<span class="l-tooltip">${r}</span>`}function addressAliasTooltip(){let t=this.getAttribute("alias-type"),e=this.getAttribute("cc");return`<span class="l-tooltip">${t}<br>${e}</span>`}window.addEventListener("DOMContentLoaded",()=>{let t=getCoinCookie();t?.length===3&&("true"===t[2]&&(changeCSSStyle(".prim-amt","display","none"),changeCSSStyle(".sec-amt","display","initial")),document.querySelectorAll(".amt").forEach(t=>new bootstrap.Tooltip(t,{title:amountTooltip,html:!0}))),document.querySelectorAll("[alias-type]").forEach(t=>new bootstrap.Tooltip(t,{title:addressAliasTooltip,html:!0})),document.querySelectorAll("[tt]").forEach(t=>new bootstrap.Tooltip(t,{title:t.getAttribute("tt")})),document.querySelectorAll("#header .bb-group>.btn-check").forEach(t=>t.addEventListener("click",t=>{let e=getCoinCookie(),l="secondary-coin"===t.target.id;e?.length===3&&"true"===e[2]!==l&&(document.cookie=`${e[0]}=${e[1]}=${l}; Path=/`,changeCSSStyle(".prim-amt","display",l?"none":"initial"),changeCSSStyle(".sec-amt","display",l?"initial":"none"))})),document.querySelectorAll(".copyable").forEach(t=>t.addEventListener("click",t=>{if(t.clientX<t.target.getBoundingClientRect().x){let e=t.target.getAttribute("cc");e||(e=t.target.innerText),navigator.clipboard.writeText(e),t.target.className=t.target.className.replace("copyable","copied"),setTimeout(()=>t.target.className=t.target.className.replace("copied","copyable"),1e3),t.preventDefault()}}))});

1
static/js/main.min.4.js Normal file
View File

@@ -0,0 +1 @@
function syntaxHighlight(t){return(t=(t=JSON.stringify(t,void 0,2)).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")).length>1e6?`<span class="key">${t}</span>`:t.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,(t=>{let e="number";return/^"/.test(t)?e=/:$/.test(t)?"key":"string":/true|false/.test(t)?e="boolean":/null/.test(t)&&(e="null"),`<span class="${e}">${t}</span>`}))}function getCoinCookie(){if(hasSecondary)return document.cookie.split("; ").find((t=>t.startsWith("secondary_coin=")))?.split("=")}function changeCSSStyle(t,e,n){const a=document.all?"rules":"cssRules";for(i=0,len=document.styleSheets[1][a].length;i<len;i++)if(document.styleSheets[1][a][i].selectorText===t)return void(document.styleSheets[1][a][i].style[e]=n)}function amountTooltip(){const t=this.querySelector(".prim-amt"),e=this.querySelector(".sec-amt"),n=this.querySelector(".csec-amt"),a=this.querySelector(".base-amt"),o=this.querySelector(".cbase-amt");let i=`${t.outerHTML}<br>`;if(a){let t=a.getAttribute("tm");t||(t="now"),i+=`<span class="amt-time">${t}</span>${a.outerHTML}<br>`}if(o&&(i+=`<span class="amt-time">now</span>${o.outerHTML}<br>`),e){let t=e.getAttribute("tm");t||(t="now"),i+=`<span class="amt-time">${t}</span>${e.outerHTML}<br>`}return n&&(i+=`<span class="amt-time">now</span>${n.outerHTML}<br>`),`<span class="l-tooltip">${i}</span>`}function addressAliasTooltip(){return`<span class="l-tooltip">${this.getAttribute("alias-type")}<br>${this.getAttribute("cc")}</span>`}function handleTxPage(t,e){const n=document.getElementById("raw"),a=document.getElementById("raw-button"),o=document.getElementById("raw-hex-button");n.innerHTML=syntaxHighlight(t);let i=!1;const r={};async function s(e){if(t.hex)return t.hex;if(r[e])return r[e];const n=await async function(t){const e=await fetch(`/api/rawtx/${t}`);if(!e.ok)throw new Error(`Error fetching data: ${e.status}`);const n=await e.text();return n.replace(/"/g,"")}(e);return r[e]=n,n}function l(){i?(a.classList.add("active"),a.style.fontWeight="normal",o.classList.remove("active"),o.style.fontWeight="bold"):(a.classList.remove("active"),a.style.fontWeight="bold",o.classList.add("active"),o.style.fontWeight="normal")}l(),o.addEventListener("click",(async()=>{if(!i){try{const t=await s(e);n.textContent=t}catch(t){console.error("Error fetching raw transaction hex:",t),n.textContent=`Error fetching raw transaction hex: ${t.message}`}i=!0,l()}})),a.addEventListener("click",(()=>{i&&(n.innerHTML=syntaxHighlight(t),i=!1,l())}))}window.addEventListener("DOMContentLoaded",(()=>{const t=getCoinCookie();3===t?.length&&("true"===t[2]&&(changeCSSStyle(".prim-amt","display","none"),changeCSSStyle(".sec-amt","display","initial")),document.querySelectorAll(".amt").forEach((t=>new bootstrap.Tooltip(t,{title:amountTooltip,html:!0})))),document.querySelectorAll("[alias-type]").forEach((t=>new bootstrap.Tooltip(t,{title:addressAliasTooltip,html:!0}))),document.querySelectorAll("[tt]").forEach((t=>new bootstrap.Tooltip(t,{title:t.getAttribute("tt")}))),document.querySelectorAll("#header .bb-group>.btn-check").forEach((t=>t.addEventListener("click",(t=>{const e=getCoinCookie(),n="secondary-coin"===t.target.id;3===e?.length&&"true"===e[2]!==n&&(document.cookie=`${e[0]}=${e[1]}=${n}; Path=/`,changeCSSStyle(".prim-amt","display",n?"none":"initial"),changeCSSStyle(".sec-amt","display",n?"initial":"none"))})))),document.querySelectorAll(".copyable").forEach((t=>t.addEventListener("click",(t=>{if(t.clientX<t.target.getBoundingClientRect().x){let e=t.target.getAttribute("cc");e||(e=t.target.innerText);const n=e.trim();navigator.clipboard.writeText(n),t.target.className=t.target.className.replace("copyable","copied"),setTimeout((()=>t.target.className=t.target.className.replace("copied","copyable")),1e3),t.preventDefault()}}))))}));

View File

@@ -182,13 +182,19 @@
{{end}}
{{end}}
<div class="pt-4">
<h5>Raw Transaction</h5>
<div class="json">
<pre id="raw"></pre>
<button style="color: black" class="btn btn-paging" id="raw-button">
Raw Transaction
</button>
<button style="color: black" class="btn btn-paging" id="raw-hex-button">
Raw Transaction Hex
</button>
<div class="json copyable">
<pre id="raw" style="text-wrap: auto;"></pre>
</div>
<script type="text/javascript">
var raw = {{$tx.CoinSpecificData}};
document.getElementById('raw').innerHTML = syntaxHighlight(raw);
<script>
const rawData = {{ $tx.CoinSpecificData }};
const txId = {{ jsStr $tx.Txid }};
handleTxPage(rawData, txId);
</script>
</div>
{{end}}

View File

@@ -139,6 +139,11 @@ func (c *fakeBlockChainEthereumType) EthereumTypeRpcCall(data, to, from string)
return data + "abcd", nil
}
// EthereumTypeGetRawTransaction returns simulated transaction hex data
func (c *fakeBlockChainEthereumType) EthereumTypeGetRawTransaction(txid string) (string, error) {
return txid + "abcd", nil
}
// GetTokenURI returns URI derived from the input contractDesc
func (c *fakeBlockChainEthereumType) GetTokenURI(contractDesc bchain.AddressDescriptor, tokenID *big.Int) (string, error) {
return "https://ipfs.io/ipfs/" + contractDesc.String()[3:] + ".json", nil