Skip to content

Commit 8560918

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 1e56076 commit 8560918

File tree

4 files changed

+152
-15
lines changed

4 files changed

+152
-15
lines changed

backend/backend.go

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"net/url"
2222
"os"
2323
"path/filepath"
24+
"reflect"
2425
"strings"
2526
"time"
2627

@@ -69,13 +70,6 @@ var fixedURLWhitelist = []string{
6970
"https://shiftcrypto.support/",
7071
// Exchange rates.
7172
"https://www.coingecko.com/",
72-
// Block explorers.
73-
"https://blockstream.info/tx/",
74-
"https://blockstream.info/testnet/tx/",
75-
"https://sochain.com/tx/LTCTEST/",
76-
"https://blockchair.com/litecoin/transaction/",
77-
"https://etherscan.io/tx/",
78-
"https://goerli.etherscan.io/tx/",
7973
// Moonpay onramp
8074
"https://www.moonpay.com/",
8175
"https://support.moonpay.com/",
@@ -482,43 +476,51 @@ func (backend *Backend) Coin(code coinpkg.Code) (coinpkg.Coin, error) {
482476
servers := backend.defaultElectrumXServers(code)
483477
coin = btc.NewCoin(coinpkg.CodeRBTC, "Bitcoin Regtest", "RBTC", coinpkg.BtcUnitDefault, &chaincfg.RegressionNetParams, dbFolder, servers, "", backend.socksProxy)
484478
case code == coinpkg.CodeTBTC:
479+
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.TBTC
485480
servers := backend.defaultElectrumXServers(code)
486481
coin = btc.NewCoin(coinpkg.CodeTBTC, "Bitcoin Testnet", "TBTC", btcFormatUnit, &chaincfg.TestNet3Params, dbFolder, servers,
487-
"https://blockstream.info/testnet/tx/", backend.socksProxy)
482+
blockExplorerPrefix, backend.socksProxy)
488483
case code == coinpkg.CodeBTC:
484+
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.BTC
489485
servers := backend.defaultElectrumXServers(code)
490486
coin = btc.NewCoin(coinpkg.CodeBTC, "Bitcoin", "BTC", btcFormatUnit, &chaincfg.MainNetParams, dbFolder, servers,
491-
"https://blockstream.info/tx/", backend.socksProxy)
487+
blockExplorerPrefix, backend.socksProxy)
492488
case code == coinpkg.CodeTLTC:
489+
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.TLTC
493490
servers := backend.defaultElectrumXServers(code)
494491
coin = btc.NewCoin(coinpkg.CodeTLTC, "Litecoin Testnet", "TLTC", coinpkg.BtcUnitDefault, &ltc.TestNet4Params, dbFolder, servers,
495-
"https://sochain.com/tx/LTCTEST/", backend.socksProxy)
492+
blockExplorerPrefix, backend.socksProxy)
496493
case code == coinpkg.CodeLTC:
494+
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.LTC
497495
servers := backend.defaultElectrumXServers(code)
498496
coin = btc.NewCoin(coinpkg.CodeLTC, "Litecoin", "LTC", coinpkg.BtcUnitDefault, &ltc.MainNetParams, dbFolder, servers,
499-
"https://blockchair.com/litecoin/transaction/", backend.socksProxy)
497+
blockExplorerPrefix, backend.socksProxy)
500498
case code == coinpkg.CodeETH:
499+
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.ETH
501500
etherScan := etherscan.NewEtherScan("https://api.etherscan.io/api", backend.etherScanHTTPClient)
502501
coin = eth.NewCoin(etherScan, code, "Ethereum", "ETH", "ETH", params.MainnetChainConfig,
503-
"https://etherscan.io/tx/",
502+
blockExplorerPrefix,
504503
etherScan,
505504
nil)
506505
case code == coinpkg.CodeGOETH:
506+
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.GOETH
507507
etherScan := etherscan.NewEtherScan("https://api-goerli.etherscan.io/api", backend.etherScanHTTPClient)
508508
coin = eth.NewCoin(etherScan, code, "Ethereum Goerli", "GOETH", "GOETH", params.GoerliChainConfig,
509-
"https://goerli.etherscan.io/tx/",
509+
blockExplorerPrefix,
510510
etherScan,
511511
nil)
512512
case code == coinpkg.CodeSEPETH:
513+
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.SEPETH
513514
etherScan := etherscan.NewEtherScan("https://api-sepolia.etherscan.io/api", backend.etherScanHTTPClient)
514515
coin = eth.NewCoin(etherScan, code, "Ethereum Sepolia", "SEPETH", "SEPETH", params.SepoliaChainConfig,
515-
"https://sepolia.etherscan.io/tx/",
516+
blockExplorerPrefix,
516517
etherScan,
517518
nil)
518519
case erc20Token != nil:
520+
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.ETH
519521
etherScan := etherscan.NewEtherScan("https://api.etherscan.io/api", backend.etherScanHTTPClient)
520522
coin = eth.NewCoin(etherScan, erc20Token.code, erc20Token.name, erc20Token.unit, "ETH", params.MainnetChainConfig,
521-
"https://etherscan.io/tx/",
523+
blockExplorerPrefix,
522524
etherScan,
523525
erc20Token.token,
524526
)
@@ -819,6 +821,16 @@ func (backend *Backend) SystemOpen(url string) error {
819821
}
820822
}
821823

824+
// Block explorers are not defined in the fixedURLWhiteList but in AvailableBlockexplorers.
825+
var allAvailableExplorers = reflect.ValueOf(config.AvailableExplorers)
826+
for i := 0; i < allAvailableExplorers.NumField(); i++ {
827+
coinAvailableExplorers := allAvailableExplorers.Field(i).Interface().([]config.BlockExplorer)
828+
for _, explorer := range coinAvailableExplorers {
829+
if strings.HasPrefix(url, explorer.Url) {
830+
return backend.environment.SystemOpen(url)
831+
}
832+
}
833+
}
822834
return errp.Newf("Blocked /open with url: %s", url)
823835
}
824836

@@ -942,3 +954,8 @@ func (backend *Backend) SetWatchonly(rootFingerprint []byte, watchonly bool) err
942954
&t,
943955
)
944956
}
957+
958+
// AvailableExplorers returns a struct containing all available block explorers for each coin.
959+
func (backend *Backend) AvailableExplorers() config.AvailableBlockExplorers {
960+
return config.AvailableExplorers
961+
}

backend/config/blockexplorer.go

Lines changed: 90 additions & 0 deletions
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

Lines changed: 21 additions & 0 deletions
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"`
@@ -83,6 +93,8 @@ type Backend struct {
8393
TLTC btcCoinConfig `json:"tltc"`
8494
ETH ethCoinConfig `json:"eth"`
8595

96+
BlockExplorers blockExplorers `json:"blockExplorers"`
97+
8698
// Removed in v4.35 - don't reuse these two keys.
8799
TETH struct{} `json:"teth"`
88100
RETH struct{} `json:"reth"`
@@ -228,6 +240,15 @@ func NewDefaultAppConfig() AppConfig {
228240
ETH: ethCoinConfig{
229241
DeprecatedActiveERC20Tokens: []string{},
230242
},
243+
BlockExplorers: blockExplorers{
244+
BTC: AvailableExplorers.Btc[0].Url,
245+
TBTC: AvailableExplorers.Tbtc[0].Url,
246+
LTC: AvailableExplorers.Ltc[0].Url,
247+
TLTC: AvailableExplorers.Tltc[0].Url,
248+
ETH: AvailableExplorers.Eth[0].Url,
249+
GOETH: AvailableExplorers.GoEth[0].Url,
250+
SEPETH: AvailableExplorers.SepEth[0].Url,
251+
},
231252
// Copied from frontend/web/src/components/rates/rates.tsx.
232253
FiatList: []string{rates.USD.String(), rates.EUR.String(), rates.CHF.String()},
233254
MainFiat: rates.USD.String(),

backend/handlers/handlers.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ type Backend interface {
103103
AOPPCancel()
104104
AOPPApprove()
105105
AOPPChooseAccount(code accountsTypes.Code)
106+
AvailableExplorers() config.AvailableBlockExplorers
106107
GetAccountFromCode(code accountsTypes.Code) (accounts.Interface, error)
107108
HTTPClient() *http.Client
108109
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")
@@ -1452,3 +1454,10 @@ func (handlers *Handlers) postOnAuthSettingChanged(r *http.Request) interface{}
14521454
handlers.backend.Config().AppConfig().Backend.Authentication)
14531455
return nil
14541456
}
1457+
1458+
// getAvailableExplorers returns a struct containing arrays with block explorers for each
1459+
// individual coin code.
1460+
func (handlers *Handlers) getAvailableExplorers(*http.Request) interface{} {
1461+
// TODO: maybe filter out testing coins if not testing and real if testing
1462+
return config.AvailableExplorers
1463+
}

0 commit comments

Comments
 (0)