Skip to content

Commit f45d117

Browse files
committed
backend: implement block explorer selection
Add the block explorer prefix url to the configuration that will be used to open the transaction. The available options to select are statically defined and the frontend can learn them by calling the available-explorers endpoint.
1 parent f1aded9 commit f45d117

File tree

4 files changed

+166
-21
lines changed

4 files changed

+166
-21
lines changed

backend/backend.go

+32-15
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"net/url"
2424
"os"
2525
"path/filepath"
26+
"reflect"
2627
"strings"
2728
"time"
2829

@@ -73,13 +74,6 @@ var fixedURLWhitelist = []string{
7374
"https://shiftcrypto.support/",
7475
// Exchange rates.
7576
"https://www.coingecko.com/",
76-
// Block explorers.
77-
"https://blockstream.info/tx/",
78-
"https://blockstream.info/testnet/tx/",
79-
"https://sochain.com/tx/LTCTEST/",
80-
"https://blockchair.com/litecoin/transaction/",
81-
"https://etherscan.io/tx/",
82-
"https://goerli.etherscan.io/tx/",
8377
// Moonpay onramp
8478
"https://www.moonpay.com/",
8579
"https://support.moonpay.com/",
@@ -485,43 +479,51 @@ func (backend *Backend) Coin(code coinpkg.Code) (coinpkg.Coin, error) {
485479
servers := backend.defaultElectrumXServers(code)
486480
coin = btc.NewCoin(coinpkg.CodeRBTC, "Bitcoin Regtest", "RBTC", coinpkg.BtcUnitDefault, &chaincfg.RegressionNetParams, dbFolder, servers, "", backend.socksProxy)
487481
case code == coinpkg.CodeTBTC:
482+
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.TBTC
488483
servers := backend.defaultElectrumXServers(code)
489484
coin = btc.NewCoin(coinpkg.CodeTBTC, "Bitcoin Testnet", "TBTC", btcFormatUnit, &chaincfg.TestNet3Params, dbFolder, servers,
490-
"https://blockstream.info/testnet/tx/", backend.socksProxy)
485+
blockExplorerPrefix, backend.socksProxy)
491486
case code == coinpkg.CodeBTC:
487+
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.BTC
492488
servers := backend.defaultElectrumXServers(code)
493489
coin = btc.NewCoin(coinpkg.CodeBTC, "Bitcoin", "BTC", btcFormatUnit, &chaincfg.MainNetParams, dbFolder, servers,
494-
"https://blockstream.info/tx/", backend.socksProxy)
490+
blockExplorerPrefix, backend.socksProxy)
495491
case code == coinpkg.CodeTLTC:
492+
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.TLTC
496493
servers := backend.defaultElectrumXServers(code)
497494
coin = btc.NewCoin(coinpkg.CodeTLTC, "Litecoin Testnet", "TLTC", coinpkg.BtcUnitDefault, &ltc.TestNet4Params, dbFolder, servers,
498-
"https://sochain.com/tx/LTCTEST/", backend.socksProxy)
495+
blockExplorerPrefix, backend.socksProxy)
499496
case code == coinpkg.CodeLTC:
497+
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.LTC
500498
servers := backend.defaultElectrumXServers(code)
501499
coin = btc.NewCoin(coinpkg.CodeLTC, "Litecoin", "LTC", coinpkg.BtcUnitDefault, &ltc.MainNetParams, dbFolder, servers,
502-
"https://blockchair.com/litecoin/transaction/", backend.socksProxy)
500+
blockExplorerPrefix, backend.socksProxy)
503501
case code == coinpkg.CodeETH:
502+
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.ETH
504503
etherScan := etherscan.NewEtherScan("https://api.etherscan.io/api", backend.etherScanHTTPClient)
505504
coin = eth.NewCoin(etherScan, code, "Ethereum", "ETH", "ETH", params.MainnetChainConfig,
506-
"https://etherscan.io/tx/",
505+
blockExplorerPrefix,
507506
etherScan,
508507
nil)
509508
case code == coinpkg.CodeGOETH:
509+
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.GOETH
510510
etherScan := etherscan.NewEtherScan("https://api-goerli.etherscan.io/api", backend.etherScanHTTPClient)
511511
coin = eth.NewCoin(etherScan, code, "Ethereum Goerli", "GOETH", "GOETH", params.GoerliChainConfig,
512-
"https://goerli.etherscan.io/tx/",
512+
blockExplorerPrefix,
513513
etherScan,
514514
nil)
515515
case code == coinpkg.CodeSEPETH:
516+
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.SEPETH
516517
etherScan := etherscan.NewEtherScan("https://api-sepolia.etherscan.io/api", backend.etherScanHTTPClient)
517518
coin = eth.NewCoin(etherScan, code, "Ethereum Sepolia", "SEPETH", "SEPETH", params.SepoliaChainConfig,
518-
"https://sepolia.etherscan.io/tx/",
519+
blockExplorerPrefix,
519520
etherScan,
520521
nil)
521522
case erc20Token != nil:
523+
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.ETH
522524
etherScan := etherscan.NewEtherScan("https://api.etherscan.io/api", backend.etherScanHTTPClient)
523525
coin = eth.NewCoin(etherScan, erc20Token.code, erc20Token.name, erc20Token.unit, "ETH", params.MainnetChainConfig,
524-
"https://etherscan.io/tx/",
526+
blockExplorerPrefix,
525527
etherScan,
526528
erc20Token.token,
527529
)
@@ -905,6 +907,16 @@ func (backend *Backend) SystemOpen(url string) error {
905907
}
906908
}
907909

910+
// Block explorers are not defined in the fixedURLWhiteList but in AvailableBlockexplorers.
911+
var allAvailableExplorers = reflect.ValueOf(config.AvailableExplorers)
912+
for i := 0; i < allAvailableExplorers.NumField(); i++ {
913+
coinAvailableExplorers := allAvailableExplorers.Field(i).Interface().([]config.BlockExplorer)
914+
for _, explorer := range coinAvailableExplorers {
915+
if strings.HasPrefix(url, explorer.Url) {
916+
return backend.environment.SystemOpen(url)
917+
}
918+
}
919+
}
908920
return errp.Newf("Blocked /open with url: %s", url)
909921
}
910922

@@ -1028,3 +1040,8 @@ func (backend *Backend) SetWatchonly(rootFingerprint []byte, watchonly bool) err
10281040
&t,
10291041
)
10301042
}
1043+
1044+
// AvailableExplorers returns a struct containing all available block explorers for each coin.
1045+
func (backend *Backend) AvailableExplorers() config.AvailableBlockExplorers {
1046+
return config.AvailableExplorers
1047+
}

backend/config/blockexplorer.go

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package config
2+
3+
// BlockExplorer defines a selectable block explorer.
4+
type BlockExplorer struct {
5+
// Name of the block explorer used for UI.
6+
Name string `json:"name"`
7+
// Url of the block explorer that the txid is appended.
8+
Url string `json:"url"`
9+
}
10+
11+
// AvailableBlockExplorers defines all available block explorers for each coin.
12+
type AvailableBlockExplorers struct {
13+
Btc []BlockExplorer `json:"btc"`
14+
Tbtc []BlockExplorer `json:"tbtc"`
15+
Ltc []BlockExplorer `json:"ltc"`
16+
Tltc []BlockExplorer `json:"tltc"`
17+
Eth []BlockExplorer `json:"eth"`
18+
GoEth []BlockExplorer `json:"goeth"`
19+
SepEth []BlockExplorer `json:"sepeth"`
20+
}
21+
22+
// AvailableExplorers FIXME: Localize AvailableExplorers.
23+
var AvailableExplorers = AvailableBlockExplorers{
24+
Btc: []BlockExplorer{
25+
{
26+
Name: "blockstream.info",
27+
Url: "https://blockstream.info/tx/",
28+
},
29+
{
30+
Name: "mempool.space",
31+
Url: "https://mempool.space/tx",
32+
},
33+
},
34+
Tbtc: []BlockExplorer{
35+
{
36+
Name: "mempool.space",
37+
Url: "https://mempool.space/testnet/tx/",
38+
},
39+
{
40+
Name: "blockstream.info",
41+
Url: "https://blockstream.info/testnet/tx/",
42+
},
43+
},
44+
Ltc: []BlockExplorer{
45+
{
46+
Name: "sochain.com",
47+
Url: "https://sochain.com/tx/",
48+
},
49+
{
50+
Name: "blockchair.com",
51+
Url: "https://blockchair.com/litecoin/transaction",
52+
},
53+
},
54+
Tltc: []BlockExplorer{
55+
{
56+
Name: "sochain.com",
57+
Url: "https://sochain.com/tx/LTCTEST/",
58+
},
59+
},
60+
Eth: []BlockExplorer{
61+
{
62+
Name: "etherscan.io",
63+
Url: "https://etherscan.io/tx/",
64+
},
65+
{
66+
Name: "ethplorer.io",
67+
Url: "https://ethplorer.io/tx/",
68+
},
69+
},
70+
GoEth: []BlockExplorer{
71+
{
72+
Name: "etherscan.io",
73+
Url: "https://goerli.etherscan.io/tx/",
74+
},
75+
{
76+
Name: "ethplorer.io",
77+
Url: "https://goerli.ethplorer.io/tx/",
78+
},
79+
},
80+
SepEth: []BlockExplorer{
81+
{
82+
Name: "etherscan.io",
83+
Url: "https://sepolia.etherscan.io/tx/",
84+
},
85+
{
86+
Name: "ethplorer.io",
87+
Url: "https://sepolia.ethplorer.io/tx/",
88+
},
89+
},
90+
}

backend/config/config.go

+35-6
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ import (
2525
"github.com/digitalbitbox/bitbox-wallet-app/util/locker"
2626
)
2727

28+
type blockExplorers struct {
29+
BTC string `json:"btc"`
30+
TBTC string `json:"tbtc"`
31+
LTC string `json:"ltc"`
32+
TLTC string `json:"tltc"`
33+
ETH string `json:"eth"`
34+
GOETH string `json:"goeth"`
35+
SEPETH string `json:"sepeth"`
36+
}
37+
2838
// ServerInfo holds information about the backend server(s).
2939
type ServerInfo struct {
3040
Server string `json:"server"`
@@ -76,12 +86,16 @@ type Backend struct {
7686

7787
Authentication bool `json:"authentication"`
7888

79-
BTC btcCoinConfig `json:"btc"`
80-
TBTC btcCoinConfig `json:"tbtc"`
81-
RBTC btcCoinConfig `json:"rbtc"`
82-
LTC btcCoinConfig `json:"ltc"`
83-
TLTC btcCoinConfig `json:"tltc"`
84-
ETH ethCoinConfig `json:"eth"`
89+
BTC btcCoinConfig `json:"btc"`
90+
TBTC btcCoinConfig `json:"tbtc"`
91+
RBTC btcCoinConfig `json:"rbtc"`
92+
LTC btcCoinConfig `json:"ltc"`
93+
TLTC btcCoinConfig `json:"tltc"`
94+
ETH ethCoinConfig `json:"eth"`
95+
GOETH ethCoinConfig `json:"goeth"`
96+
SEPETH ethCoinConfig `json:"sepeth"`
97+
98+
BlockExplorers blockExplorers `json:"blockExplorers"`
8599

86100
// Removed in v4.35 - don't reuse these two keys.
87101
TETH struct{} `json:"teth"`
@@ -228,6 +242,21 @@ func NewDefaultAppConfig() AppConfig {
228242
ETH: ethCoinConfig{
229243
DeprecatedActiveERC20Tokens: []string{},
230244
},
245+
GOETH: ethCoinConfig{
246+
DeprecatedActiveERC20Tokens: []string{},
247+
},
248+
SEPETH: ethCoinConfig{
249+
DeprecatedActiveERC20Tokens: []string{},
250+
},
251+
BlockExplorers: blockExplorers{
252+
BTC: AvailableExplorers.Btc[0].Url,
253+
TBTC: AvailableExplorers.Tbtc[0].Url,
254+
LTC: AvailableExplorers.Ltc[0].Url,
255+
TLTC: AvailableExplorers.Tltc[0].Url,
256+
ETH: AvailableExplorers.Eth[0].Url,
257+
GOETH: AvailableExplorers.GoEth[0].Url,
258+
SEPETH: AvailableExplorers.SepEth[0].Url,
259+
},
231260
// Copied from frontend/web/src/components/rates/rates.tsx.
232261
FiatList: []string{rates.USD.String(), rates.EUR.String(), rates.CHF.String()},
233262
MainFiat: rates.USD.String(),

backend/handlers/handlers.go

+9
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ type Backend interface {
104104
AOPPCancel()
105105
AOPPApprove()
106106
AOPPChooseAccount(code accountsTypes.Code)
107+
AvailableExplorers() config.AvailableBlockExplorers
107108
GetAccountFromCode(code accountsTypes.Code) (accounts.Interface, error)
108109
HTTPClient() *http.Client
109110
LookupInsuredAccounts(accountCode accountsTypes.Code) ([]bitsurance.AccountDetails, error)
@@ -250,6 +251,7 @@ func NewHandlers(
250251
getAPIRouterNoError(apiRouter)("/set-watchonly", handlers.postSetWatchonly).Methods("POST")
251252
getAPIRouterNoError(apiRouter)("/on-auth-setting-changed", handlers.postOnAuthSettingChanged).Methods("POST")
252253
getAPIRouterNoError(apiRouter)("/accounts/eth-account-code", handlers.lookupEthAccountCode).Methods("POST")
254+
getAPIRouterNoError(apiRouter)("/available-explorers", handlers.getAvailableExplorers).Methods("GET")
253255

254256
devicesRouter := getAPIRouterNoError(apiRouter.PathPrefix("/devices").Subrouter())
255257
devicesRouter("/registered", handlers.getDevicesRegistered).Methods("GET")
@@ -1400,3 +1402,10 @@ func (handlers *Handlers) postOnAuthSettingChanged(r *http.Request) interface{}
14001402
handlers.backend.Config().AppConfig().Backend.Authentication)
14011403
return nil
14021404
}
1405+
1406+
// getAvailableExplorers returns a struct containing arrays with block explorers for each
1407+
// individual coin code.
1408+
func (handlers *Handlers) getAvailableExplorers(*http.Request) interface{} {
1409+
// TODO: maybe filter out testing coins if not testing and real if testing
1410+
return config.AvailableExplorers
1411+
}

0 commit comments

Comments
 (0)