Skip to content

Commit 2526f91

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 2526f91

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
@@ -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

+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
@@ -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)