Skip to content

Commit

Permalink
backend: implement block explorer selection
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
NicolaLS committed Feb 26, 2024
1 parent f1aded9 commit f45d117
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 21 deletions.
47 changes: 32 additions & 15 deletions backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"net/url"
"os"
"path/filepath"
"reflect"
"strings"
"time"

Expand Down Expand Up @@ -73,13 +74,6 @@ var fixedURLWhitelist = []string{
"https://shiftcrypto.support/",
// Exchange rates.
"https://www.coingecko.com/",
// Block explorers.
"https://blockstream.info/tx/",
"https://blockstream.info/testnet/tx/",
"https://sochain.com/tx/LTCTEST/",
"https://blockchair.com/litecoin/transaction/",
"https://etherscan.io/tx/",
"https://goerli.etherscan.io/tx/",
// Moonpay onramp
"https://www.moonpay.com/",
"https://support.moonpay.com/",
Expand Down Expand Up @@ -485,43 +479,51 @@ func (backend *Backend) Coin(code coinpkg.Code) (coinpkg.Coin, error) {
servers := backend.defaultElectrumXServers(code)
coin = btc.NewCoin(coinpkg.CodeRBTC, "Bitcoin Regtest", "RBTC", coinpkg.BtcUnitDefault, &chaincfg.RegressionNetParams, dbFolder, servers, "", backend.socksProxy)
case code == coinpkg.CodeTBTC:
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.TBTC
servers := backend.defaultElectrumXServers(code)
coin = btc.NewCoin(coinpkg.CodeTBTC, "Bitcoin Testnet", "TBTC", btcFormatUnit, &chaincfg.TestNet3Params, dbFolder, servers,
"https://blockstream.info/testnet/tx/", backend.socksProxy)
blockExplorerPrefix, backend.socksProxy)
case code == coinpkg.CodeBTC:
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.BTC
servers := backend.defaultElectrumXServers(code)
coin = btc.NewCoin(coinpkg.CodeBTC, "Bitcoin", "BTC", btcFormatUnit, &chaincfg.MainNetParams, dbFolder, servers,
"https://blockstream.info/tx/", backend.socksProxy)
blockExplorerPrefix, backend.socksProxy)
case code == coinpkg.CodeTLTC:
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.TLTC
servers := backend.defaultElectrumXServers(code)
coin = btc.NewCoin(coinpkg.CodeTLTC, "Litecoin Testnet", "TLTC", coinpkg.BtcUnitDefault, &ltc.TestNet4Params, dbFolder, servers,
"https://sochain.com/tx/LTCTEST/", backend.socksProxy)
blockExplorerPrefix, backend.socksProxy)
case code == coinpkg.CodeLTC:
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.LTC
servers := backend.defaultElectrumXServers(code)
coin = btc.NewCoin(coinpkg.CodeLTC, "Litecoin", "LTC", coinpkg.BtcUnitDefault, &ltc.MainNetParams, dbFolder, servers,
"https://blockchair.com/litecoin/transaction/", backend.socksProxy)
blockExplorerPrefix, backend.socksProxy)
case code == coinpkg.CodeETH:
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.ETH
etherScan := etherscan.NewEtherScan("https://api.etherscan.io/api", backend.etherScanHTTPClient)
coin = eth.NewCoin(etherScan, code, "Ethereum", "ETH", "ETH", params.MainnetChainConfig,
"https://etherscan.io/tx/",
blockExplorerPrefix,
etherScan,
nil)
case code == coinpkg.CodeGOETH:
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.GOETH
etherScan := etherscan.NewEtherScan("https://api-goerli.etherscan.io/api", backend.etherScanHTTPClient)
coin = eth.NewCoin(etherScan, code, "Ethereum Goerli", "GOETH", "GOETH", params.GoerliChainConfig,
"https://goerli.etherscan.io/tx/",
blockExplorerPrefix,
etherScan,
nil)
case code == coinpkg.CodeSEPETH:
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.SEPETH
etherScan := etherscan.NewEtherScan("https://api-sepolia.etherscan.io/api", backend.etherScanHTTPClient)
coin = eth.NewCoin(etherScan, code, "Ethereum Sepolia", "SEPETH", "SEPETH", params.SepoliaChainConfig,
"https://sepolia.etherscan.io/tx/",
blockExplorerPrefix,
etherScan,
nil)
case erc20Token != nil:
blockExplorerPrefix := backend.config.AppConfig().Backend.BlockExplorers.ETH
etherScan := etherscan.NewEtherScan("https://api.etherscan.io/api", backend.etherScanHTTPClient)
coin = eth.NewCoin(etherScan, erc20Token.code, erc20Token.name, erc20Token.unit, "ETH", params.MainnetChainConfig,
"https://etherscan.io/tx/",
blockExplorerPrefix,
etherScan,
erc20Token.token,
)
Expand Down Expand Up @@ -905,6 +907,16 @@ func (backend *Backend) SystemOpen(url string) error {
}
}

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

Expand Down Expand Up @@ -1028,3 +1040,8 @@ func (backend *Backend) SetWatchonly(rootFingerprint []byte, watchonly bool) err
&t,
)
}

// AvailableExplorers returns a struct containing all available block explorers for each coin.
func (backend *Backend) AvailableExplorers() config.AvailableBlockExplorers {
return config.AvailableExplorers
}
90 changes: 90 additions & 0 deletions backend/config/blockexplorer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package config

// BlockExplorer defines a selectable block explorer.
type BlockExplorer struct {
// Name of the block explorer used for UI.
Name string `json:"name"`
// Url of the block explorer that the txid is appended.
Url string `json:"url"`
}

// AvailableBlockExplorers defines all available block explorers for each coin.
type AvailableBlockExplorers struct {
Btc []BlockExplorer `json:"btc"`
Tbtc []BlockExplorer `json:"tbtc"`
Ltc []BlockExplorer `json:"ltc"`
Tltc []BlockExplorer `json:"tltc"`
Eth []BlockExplorer `json:"eth"`
GoEth []BlockExplorer `json:"goeth"`
SepEth []BlockExplorer `json:"sepeth"`
}

// AvailableExplorers FIXME: Localize AvailableExplorers.
var AvailableExplorers = AvailableBlockExplorers{
Btc: []BlockExplorer{
{
Name: "blockstream.info",
Url: "https://blockstream.info/tx/",
},
{
Name: "mempool.space",
Url: "https://mempool.space/tx",
},
},
Tbtc: []BlockExplorer{
{
Name: "mempool.space",
Url: "https://mempool.space/testnet/tx/",
},
{
Name: "blockstream.info",
Url: "https://blockstream.info/testnet/tx/",
},
},
Ltc: []BlockExplorer{
{
Name: "sochain.com",
Url: "https://sochain.com/tx/",
},
{
Name: "blockchair.com",
Url: "https://blockchair.com/litecoin/transaction",
},
},
Tltc: []BlockExplorer{
{
Name: "sochain.com",
Url: "https://sochain.com/tx/LTCTEST/",
},
},
Eth: []BlockExplorer{
{
Name: "etherscan.io",
Url: "https://etherscan.io/tx/",
},
{
Name: "ethplorer.io",
Url: "https://ethplorer.io/tx/",
},
},
GoEth: []BlockExplorer{
{
Name: "etherscan.io",
Url: "https://goerli.etherscan.io/tx/",
},
{
Name: "ethplorer.io",
Url: "https://goerli.ethplorer.io/tx/",
},
},
SepEth: []BlockExplorer{
{
Name: "etherscan.io",
Url: "https://sepolia.etherscan.io/tx/",
},
{
Name: "ethplorer.io",
Url: "https://sepolia.ethplorer.io/tx/",
},
},
}
41 changes: 35 additions & 6 deletions backend/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ import (
"github.com/digitalbitbox/bitbox-wallet-app/util/locker"
)

type blockExplorers struct {
BTC string `json:"btc"`
TBTC string `json:"tbtc"`
LTC string `json:"ltc"`
TLTC string `json:"tltc"`
ETH string `json:"eth"`
GOETH string `json:"goeth"`
SEPETH string `json:"sepeth"`
}

// ServerInfo holds information about the backend server(s).
type ServerInfo struct {
Server string `json:"server"`
Expand Down Expand Up @@ -76,12 +86,16 @@ type Backend struct {

Authentication bool `json:"authentication"`

BTC btcCoinConfig `json:"btc"`
TBTC btcCoinConfig `json:"tbtc"`
RBTC btcCoinConfig `json:"rbtc"`
LTC btcCoinConfig `json:"ltc"`
TLTC btcCoinConfig `json:"tltc"`
ETH ethCoinConfig `json:"eth"`
BTC btcCoinConfig `json:"btc"`
TBTC btcCoinConfig `json:"tbtc"`
RBTC btcCoinConfig `json:"rbtc"`
LTC btcCoinConfig `json:"ltc"`
TLTC btcCoinConfig `json:"tltc"`
ETH ethCoinConfig `json:"eth"`
GOETH ethCoinConfig `json:"goeth"`
SEPETH ethCoinConfig `json:"sepeth"`

BlockExplorers blockExplorers `json:"blockExplorers"`

// Removed in v4.35 - don't reuse these two keys.
TETH struct{} `json:"teth"`
Expand Down Expand Up @@ -228,6 +242,21 @@ func NewDefaultAppConfig() AppConfig {
ETH: ethCoinConfig{
DeprecatedActiveERC20Tokens: []string{},
},
GOETH: ethCoinConfig{
DeprecatedActiveERC20Tokens: []string{},
},
SEPETH: ethCoinConfig{
DeprecatedActiveERC20Tokens: []string{},
},
BlockExplorers: blockExplorers{
BTC: AvailableExplorers.Btc[0].Url,
TBTC: AvailableExplorers.Tbtc[0].Url,
LTC: AvailableExplorers.Ltc[0].Url,
TLTC: AvailableExplorers.Tltc[0].Url,
ETH: AvailableExplorers.Eth[0].Url,
GOETH: AvailableExplorers.GoEth[0].Url,
SEPETH: AvailableExplorers.SepEth[0].Url,
},
// Copied from frontend/web/src/components/rates/rates.tsx.
FiatList: []string{rates.USD.String(), rates.EUR.String(), rates.CHF.String()},
MainFiat: rates.USD.String(),
Expand Down
9 changes: 9 additions & 0 deletions backend/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ type Backend interface {
AOPPCancel()
AOPPApprove()
AOPPChooseAccount(code accountsTypes.Code)
AvailableExplorers() config.AvailableBlockExplorers
GetAccountFromCode(code accountsTypes.Code) (accounts.Interface, error)
HTTPClient() *http.Client
LookupInsuredAccounts(accountCode accountsTypes.Code) ([]bitsurance.AccountDetails, error)
Expand Down Expand Up @@ -250,6 +251,7 @@ func NewHandlers(
getAPIRouterNoError(apiRouter)("/set-watchonly", handlers.postSetWatchonly).Methods("POST")
getAPIRouterNoError(apiRouter)("/on-auth-setting-changed", handlers.postOnAuthSettingChanged).Methods("POST")
getAPIRouterNoError(apiRouter)("/accounts/eth-account-code", handlers.lookupEthAccountCode).Methods("POST")
getAPIRouterNoError(apiRouter)("/available-explorers", handlers.getAvailableExplorers).Methods("GET")

devicesRouter := getAPIRouterNoError(apiRouter.PathPrefix("/devices").Subrouter())
devicesRouter("/registered", handlers.getDevicesRegistered).Methods("GET")
Expand Down Expand Up @@ -1400,3 +1402,10 @@ func (handlers *Handlers) postOnAuthSettingChanged(r *http.Request) interface{}
handlers.backend.Config().AppConfig().Backend.Authentication)
return nil
}

// getAvailableExplorers returns a struct containing arrays with block explorers for each
// individual coin code.
func (handlers *Handlers) getAvailableExplorers(*http.Request) interface{} {
// TODO: maybe filter out testing coins if not testing and real if testing
return config.AvailableExplorers
}

0 comments on commit f45d117

Please sign in to comment.