diff --git a/backend/backend.go b/backend/backend.go index 36fc3ad82a..2f7775bdd2 100644 --- a/backend/backend.go +++ b/backend/backend.go @@ -22,6 +22,7 @@ import ( "net/url" "os" "path/filepath" + "reflect" "strings" "time" @@ -71,14 +72,7 @@ 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/", - "https://sepolia.etherscan.io/tx/", + // Moonpay onramp "https://www.moonpay.com/", "https://support.moonpay.com/", @@ -490,43 +484,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, <c.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, <c.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, ) @@ -827,6 +829,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) } @@ -997,4 +1009,10 @@ func (backend *Backend) ExportLogs() error { return err } return nil + +} + +// AvailableExplorers returns a struct containing all available block explorers for each coin. +func (backend *Backend) AvailableExplorers() config.AvailableBlockExplorers { + return config.AvailableExplorers } diff --git a/backend/config/blockexplorer.go b/backend/config/blockexplorer.go new file mode 100644 index 0000000000..a54a9bc082 --- /dev/null +++ b/backend/config/blockexplorer.go @@ -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/", + }, + }, +} diff --git a/backend/config/config.go b/backend/config/config.go index 8ea963402e..ead276e27e 100644 --- a/backend/config/config.go +++ b/backend/config/config.go @@ -25,6 +25,16 @@ import ( "github.com/BitBoxSwiss/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"` @@ -83,6 +93,8 @@ type Backend struct { TLTC btcCoinConfig `json:"tltc"` ETH ethCoinConfig `json:"eth"` + BlockExplorers blockExplorers `json:"blockExplorers"` + // Removed in v4.35 - don't reuse these two keys. TETH struct{} `json:"teth"` RETH struct{} `json:"reth"` @@ -228,6 +240,15 @@ func NewDefaultAppConfig() AppConfig { ETH: 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(), diff --git a/backend/handlers/handlers.go b/backend/handlers/handlers.go index b7a643677c..7937e63ad5 100644 --- a/backend/handlers/handlers.go +++ b/backend/handlers/handlers.go @@ -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) @@ -252,6 +253,7 @@ func NewHandlers( getAPIRouterNoError(apiRouter)("/on-auth-setting-changed", handlers.postOnAuthSettingChanged).Methods("POST") getAPIRouterNoError(apiRouter)("/export-log", handlers.postExportLog).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") @@ -1460,3 +1462,10 @@ func (handlers *Handlers) postExportLog(r *http.Request) interface{} { } return result{Success: true} } + +// 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 +} diff --git a/frontends/web/src/routes/settings/select-explorer.tsx b/frontends/web/src/routes/settings/select-explorer.tsx index 6cf0c8d165..f6620aad4c 100644 --- a/frontends/web/src/routes/settings/select-explorer.tsx +++ b/frontends/web/src/routes/settings/select-explorer.tsx @@ -122,9 +122,9 @@ export const SelectExplorerSettings = ({ accounts }: TSelectExplorerSettingsProp - - - + + +