Skip to content

Commit 4b61268

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 for which a handler was implemented in this commit.
1 parent afd8ca9 commit 4b61268

File tree

4 files changed

+154
-16
lines changed

4 files changed

+154
-16
lines changed

backend/backend.go

+34-16
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"net/url"
2323
"os"
2424
"path/filepath"
25+
"reflect"
2526
"strings"
2627
"time"
2728

@@ -71,14 +72,7 @@ var fixedURLWhitelist = []string{
7172
"https://shiftcrypto.support/",
7273
// Exchange rates.
7374
"https://www.coingecko.com/",
74-
// Block explorers.
75-
"https://blockstream.info/tx/",
76-
"https://blockstream.info/testnet/tx/",
77-
"https://sochain.com/tx/LTCTEST/",
78-
"https://blockchair.com/litecoin/transaction/",
79-
"https://etherscan.io/tx/",
80-
"https://goerli.etherscan.io/tx/",
81-
"https://sepolia.etherscan.io/tx/",
75+
8276
// Moonpay onramp
8377
"https://www.moonpay.com/",
8478
"https://support.moonpay.com/",
@@ -490,43 +484,51 @@ func (backend *Backend) Coin(code coinpkg.Code) (coinpkg.Coin, error) {
490484
servers := backend.defaultElectrumXServers(code)
491485
coin = btc.NewCoin(coinpkg.CodeRBTC, "Bitcoin Regtest", "RBTC", coinpkg.BtcUnitDefault, &chaincfg.RegressionNetParams, dbFolder, servers, "", backend.socksProxy)
492486
case code == coinpkg.CodeTBTC:
487+
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.TBTC
493488
servers := backend.defaultElectrumXServers(code)
494489
coin = btc.NewCoin(coinpkg.CodeTBTC, "Bitcoin Testnet", "TBTC", btcFormatUnit, &chaincfg.TestNet3Params, dbFolder, servers,
495-
"https://blockstream.info/testnet/tx/", backend.socksProxy)
490+
blockExplorerPrefix, backend.socksProxy)
496491
case code == coinpkg.CodeBTC:
492+
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.BTC
497493
servers := backend.defaultElectrumXServers(code)
498494
coin = btc.NewCoin(coinpkg.CodeBTC, "Bitcoin", "BTC", btcFormatUnit, &chaincfg.MainNetParams, dbFolder, servers,
499-
"https://blockstream.info/tx/", backend.socksProxy)
495+
blockExplorerPrefix, backend.socksProxy)
500496
case code == coinpkg.CodeTLTC:
497+
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.TLTC
501498
servers := backend.defaultElectrumXServers(code)
502499
coin = btc.NewCoin(coinpkg.CodeTLTC, "Litecoin Testnet", "TLTC", coinpkg.BtcUnitDefault, &ltc.TestNet4Params, dbFolder, servers,
503-
"https://sochain.com/tx/LTCTEST/", backend.socksProxy)
500+
blockExplorerPrefix, backend.socksProxy)
504501
case code == coinpkg.CodeLTC:
502+
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.LTC
505503
servers := backend.defaultElectrumXServers(code)
506504
coin = btc.NewCoin(coinpkg.CodeLTC, "Litecoin", "LTC", coinpkg.BtcUnitDefault, &ltc.MainNetParams, dbFolder, servers,
507-
"https://blockchair.com/litecoin/transaction/", backend.socksProxy)
505+
blockExplorerPrefix, backend.socksProxy)
508506
case code == coinpkg.CodeETH:
507+
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.ETH
509508
etherScan := etherscan.NewEtherScan("https://api.etherscan.io/api", backend.etherScanHTTPClient)
510509
coin = eth.NewCoin(etherScan, code, "Ethereum", "ETH", "ETH", params.MainnetChainConfig,
511-
"https://etherscan.io/tx/",
510+
blockExplorerPrefix,
512511
etherScan,
513512
nil)
514513
case code == coinpkg.CodeGOETH:
514+
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.GOETH
515515
etherScan := etherscan.NewEtherScan("https://api-goerli.etherscan.io/api", backend.etherScanHTTPClient)
516516
coin = eth.NewCoin(etherScan, code, "Ethereum Goerli", "GOETH", "GOETH", params.GoerliChainConfig,
517-
"https://goerli.etherscan.io/tx/",
517+
blockExplorerPrefix,
518518
etherScan,
519519
nil)
520520
case code == coinpkg.CodeSEPETH:
521+
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.SEPETH
521522
etherScan := etherscan.NewEtherScan("https://api-sepolia.etherscan.io/api", backend.etherScanHTTPClient)
522523
coin = eth.NewCoin(etherScan, code, "Ethereum Sepolia", "SEPETH", "SEPETH", params.SepoliaChainConfig,
523-
"https://sepolia.etherscan.io/tx/",
524+
blockExplorerPrefix,
524525
etherScan,
525526
nil)
526527
case erc20Token != nil:
528+
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.ETH
527529
etherScan := etherscan.NewEtherScan("https://api.etherscan.io/api", backend.etherScanHTTPClient)
528530
coin = eth.NewCoin(etherScan, erc20Token.code, erc20Token.name, erc20Token.unit, "ETH", params.MainnetChainConfig,
529-
"https://etherscan.io/tx/",
531+
blockExplorerPrefix,
530532
etherScan,
531533
erc20Token.token,
532534
)
@@ -827,6 +829,16 @@ func (backend *Backend) SystemOpen(url string) error {
827829
}
828830
}
829831

832+
// Block explorers are not defined in the fixedURLWhiteList but in AvailableBlockexplorers.
833+
var allAvailableExplorers = reflect.ValueOf(config.AvailableExplorers)
834+
for i := 0; i < allAvailableExplorers.NumField(); i++ {
835+
coinAvailableExplorers := allAvailableExplorers.Field(i).Interface().([]config.BlockExplorer)
836+
for _, explorer := range coinAvailableExplorers {
837+
if strings.HasPrefix(url, explorer.Url) {
838+
return backend.environment.SystemOpen(url)
839+
}
840+
}
841+
}
830842
return errp.Newf("Blocked /open with url: %s", url)
831843
}
832844

@@ -997,4 +1009,10 @@ func (backend *Backend) ExportLogs() error {
9971009
return err
9981010
}
9991011
return nil
1012+
1013+
}
1014+
1015+
// AvailableExplorers returns a struct containing all available block explorers for each coin.
1016+
func (backend *Backend) AvailableExplorers() config.AvailableBlockExplorers {
1017+
return config.AvailableExplorers
10001018
}

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

+21
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ import (
2525
"github.com/BitBoxSwiss/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

+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)
@@ -252,6 +253,7 @@ func NewHandlers(
252253
getAPIRouterNoError(apiRouter)("/on-auth-setting-changed", handlers.postOnAuthSettingChanged).Methods("POST")
253254
getAPIRouterNoError(apiRouter)("/export-log", handlers.postExportLog).Methods("POST")
254255
getAPIRouterNoError(apiRouter)("/accounts/eth-account-code", handlers.lookupEthAccountCode).Methods("POST")
256+
getAPIRouterNoError(apiRouter)("/available-explorers", handlers.getAvailableExplorers).Methods("GET")
255257

256258
devicesRouter := getAPIRouterNoError(apiRouter.PathPrefix("/devices").Subrouter())
257259
devicesRouter("/registered", handlers.getDevicesRegistered).Methods("GET")
@@ -1460,3 +1462,10 @@ func (handlers *Handlers) postExportLog(r *http.Request) interface{} {
14601462
}
14611463
return result{Success: true}
14621464
}
1465+
1466+
// getAvailableExplorers returns a struct containing arrays with block explorers for each
1467+
// individual coin code.
1468+
func (handlers *Handlers) getAvailableExplorers(*http.Request) interface{} {
1469+
// TODO: maybe filter out testing coins if not testing and real if testing
1470+
return config.AvailableExplorers
1471+
}

0 commit comments

Comments
 (0)