Skip to content

Commit

Permalink
backend: add BtcDirect to exchange deals.
Browse files Browse the repository at this point in the history
Remove the hardcoded BtcDirect widget from the frontend code,
ad instead add it to the returned list of exchange deals in
the backend.
  • Loading branch information
bznein committed Jan 23, 2025
1 parent 1fae016 commit c41e672
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 50 deletions.
44 changes: 39 additions & 5 deletions backend/exchanges/btcdirect.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,59 @@ import (
"github.com/BitBoxSwiss/bitbox-wallet-app/backend/coins/coin"
)

const (
// BTCDirectName is the name of the exchange, it is unique among all the supported exchanges.
BTCDirectName = "btcdirect"
)

var regions = []string{
"AD", "AT", "BE", "BG", "CH", "CZ", "DE", "DK", "EE", "ES", "FI", "FR", "GR",
"HR", "HU", "IS", "IT", "LI", "LT", "LU", "LV", "MD", "ME", "MK", "NL", "NO",
"PL", "PT", "RO", "RS", "SE", "SI", "SK", "SM"}

// GetBtcDirectSupportedRegions reutrn a string slice of the regions where BTC direct services
// GetBtcDirectSupportedRegions returns a string slice of the regions where BTC direct services
// are available.
func GetBtcDirectSupportedRegions() []string {
return regions
}

// IsBtcDirectSupported is true if coin.Code and region are supported by BtcDirect.
func IsBtcDirectSupported(coinCode coin.Code, region string) bool {
// isRegionSupportedBtcDirect returns whether a specific region (or an empty one
// for "unspecified") is supported by BtcDirect.
func isRegionSupportedBtcDirect(region string) bool {
return len(region) == 0 || slices.Contains(regions, region)
}

// IsBtcDirectSupported is true if coin.Code is supported by BtcDirect.
func IsBtcDirectSupported(coinCode coin.Code) bool {
supportedCoins := []coin.Code{
coin.CodeBTC, coin.CodeTBTC, coin.CodeLTC, coin.CodeTLTC, coin.CodeETH, coin.CodeSEPETH,
"eth-erc20-usdt", "eth-erc20-usdc", "eth-erc20-link"}

coinSupported := slices.Contains(supportedCoins, coinCode)
regionSupported := len(region) == 0 || slices.Contains(regions, region)

return coinSupported && regionSupported
return coinSupported
}

// IsBtcDirectOTCSupportedForCoinInRegion returns whether Btc Direct OTC is supported for the specific
// combination of coin and region.
func IsBtcDirectOTCSupportedForCoinInRegion(coinCode coin.Code, region string) bool {
return IsBtcDirectSupported(coinCode) && isRegionSupportedBtcDirect(region)
}

// BtcDirectDeals returns the purchase conditions (fee and payment methods) offered by BTCDirect.
func BtcDirectDeals() *ExchangeDealsList {
return &ExchangeDealsList{
ExchangeName: BTCDirectName,
Deals: []*ExchangeDeal{
{
Fee: 3,
Payment: CardPayment,
IsFast: true,
},
{
Fee: 2,
Payment: BankTransferPayment,
},
},
}
}
35 changes: 26 additions & 9 deletions backend/exchanges/exchanges.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package exchanges

import (
"net/http"
"slices"

"github.com/BitBoxSwiss/bitbox-wallet-app/backend/accounts"
"github.com/BitBoxSwiss/bitbox-wallet-app/util/errp"
Expand Down Expand Up @@ -86,9 +87,10 @@ type ExchangeRegionList struct {
// ExchangeRegion contains the ISO 3166-1 alpha-2 code of a specific region and a boolean
// for each exchange, indicating if that exchange is enabled for the region.
type ExchangeRegion struct {
Code string `json:"code"`
IsMoonpayEnabled bool `json:"isMoonpayEnabled"`
IsPocketEnabled bool `json:"isPocketEnabled"`
Code string `json:"code"`
IsMoonpayEnabled bool `json:"isMoonpayEnabled"`
IsPocketEnabled bool `json:"isPocketEnabled"`
IsBtcDirectEnabled bool `json:"isBtcDirectEnabled"`
}

// PaymentMethod type is used for payment options in exchange deals.
Expand Down Expand Up @@ -135,8 +137,11 @@ func ListExchangesByRegion(account accounts.Interface, httpClient *http.Client)
log.Error(pocketError)
}

btcDirectRegions := GetBtcDirectSupportedRegions()

isMoonpaySupported := IsMoonpaySupported(account.Coin().Code())
isPocketSupported := IsPocketSupported(account.Coin().Code())
isBtcDirectSupported := IsBtcDirectSupported(account.Coin().Code())

exchangeRegions := ExchangeRegionList{}
for _, code := range regionCodes {
Expand All @@ -148,10 +153,13 @@ func ListExchangesByRegion(account accounts.Interface, httpClient *http.Client)
if pocketError == nil {
_, pocketEnabled = pocketRegions[code]
}
btcDirectEnabled := slices.Contains(btcDirectRegions, code)

exchangeRegions.Regions = append(exchangeRegions.Regions, ExchangeRegion{
Code: code,
IsMoonpayEnabled: moonpayEnabled && isMoonpaySupported,
IsPocketEnabled: pocketEnabled && isPocketSupported,
Code: code,
IsMoonpayEnabled: moonpayEnabled && isMoonpaySupported,
IsPocketEnabled: pocketEnabled && isPocketSupported,
IsBtcDirectEnabled: btcDirectEnabled && isBtcDirectSupported,
})
}

Expand All @@ -162,7 +170,8 @@ func ListExchangesByRegion(account accounts.Interface, httpClient *http.Client)
func GetExchangeDeals(account accounts.Interface, regionCode string, action ExchangeAction, httpClient *http.Client) ([]*ExchangeDealsList, error) {
moonpaySupportsCoin := IsMoonpaySupported(account.Coin().Code()) && action == BuyAction
pocketSupportsCoin := IsPocketSupported(account.Coin().Code())
coinSupported := moonpaySupportsCoin || pocketSupportsCoin
btcDirectSupportsCoin := IsBtcDirectSupported(account.Coin().Code()) && action == BuyAction
coinSupported := moonpaySupportsCoin || pocketSupportsCoin || btcDirectSupportsCoin
if !coinSupported {
return nil, ErrCoinNotSupported
}
Expand All @@ -181,8 +190,9 @@ func GetExchangeDeals(account accounts.Interface, regionCode string, action Exch
}
if userRegion == nil {
userRegion = &ExchangeRegion{
IsMoonpayEnabled: true,
IsPocketEnabled: true,
IsMoonpayEnabled: true,
IsPocketEnabled: true,
IsBtcDirectEnabled: true,
}
}

Expand All @@ -200,6 +210,13 @@ func GetExchangeDeals(account accounts.Interface, regionCode string, action Exch
exchangeDealsLists = append(exchangeDealsLists, deals)
}
}
if btcDirectSupportsCoin && userRegion.IsBtcDirectEnabled {
deals := BtcDirectDeals()
if deals != nil {
exchangeDealsLists = append(exchangeDealsLists, deals)
}
}

if len(exchangeDealsLists) == 0 {
return nil, ErrRegionNotSupported
}
Expand Down
9 changes: 6 additions & 3 deletions backend/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ func NewHandlers(
getAPIRouterNoError(apiRouter)("/exchange/by-region/{code}", handlers.getExchangesByRegion).Methods("GET")
getAPIRouterNoError(apiRouter)("/exchange/deals/{action}/{code}", handlers.getExchangeDeals).Methods("GET")
getAPIRouterNoError(apiRouter)("/exchange/supported/{code}", handlers.getExchangeSupported).Methods("GET")
getAPIRouterNoError(apiRouter)("/exchange/btcdirect/supported/{code}", handlers.getBtcDirectSupported).Methods("GET")
getAPIRouterNoError(apiRouter)("/exchange/btcdirect-otc/supported/{code}", handlers.getExchangeBtcDirectOTCSupported).Methods("GET")
getAPIRouter(apiRouter)("/exchange/moonpay/buy-info/{code}", handlers.getExchangeMoonpayBuyInfo).Methods("GET")
getAPIRouterNoError(apiRouter)("/exchange/pocket/api-url/{action}", handlers.getExchangePocketURL).Methods("GET")
getAPIRouterNoError(apiRouter)("/exchange/pocket/verify-address", handlers.postPocketWidgetVerifyAddress).Methods("POST")
Expand Down Expand Up @@ -1346,7 +1346,7 @@ func (handlers *Handlers) getExchangeDeals(r *http.Request) interface{} {
}
}

func (handlers *Handlers) getBtcDirectSupported(r *http.Request) interface{} {
func (handlers *Handlers) getExchangeBtcDirectOTCSupported(r *http.Request) interface{} {
type Result struct {
Supported bool `json:"supported"`
Success bool `json:"success"`
Expand All @@ -1370,7 +1370,7 @@ func (handlers *Handlers) getBtcDirectSupported(r *http.Request) interface{} {
regionCode := r.URL.Query().Get("region")
return Result{
Success: true,
Supported: exchanges.IsBtcDirectSupported(acct.Coin().Code(), regionCode),
Supported: exchanges.IsBtcDirectOTCSupportedForCoinInRegion(acct.Coin().Code(), regionCode),
}
}

Expand All @@ -1396,6 +1396,9 @@ func (handlers *Handlers) getExchangeSupported(r *http.Request) interface{} {
if exchanges.IsPocketSupported(acct.Coin().Code()) {
supported.Exchanges = append(supported.Exchanges, exchanges.PocketName)
}
if exchanges.IsBtcDirectSupported(acct.Coin().Code()) {
supported.Exchanges = append(supported.Exchanges, exchanges.BTCDirectName)
}

return supported
}
Expand Down
2 changes: 1 addition & 1 deletion frontends/web/src/api/exchanges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,6 @@ export type TBtcDirectResponse = {

export const getBtcDirectOTCSupported = (code: AccountCode, region: ExchangeRegion['code']) => {
return (): Promise<TBtcDirectResponse> => {
return apiGet(`exchange/btcdirect/supported/${code}?region=${region}`);
return apiGet(`exchange/btcdirect-otc/supported/${code}?region=${region}`);
};
};
32 changes: 0 additions & 32 deletions frontends/web/src/routes/exchange/components/buysell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -162,38 +162,6 @@ export const BuySell = ({
onClickInfoButton={() => setInfo(buildInfo(exchange))}
/>
))}
{/* TODO: 'BTC Direct' should come from exchangeDealsResponse */}
{ action === 'buy' && (
<ExchangeSelectionRadio
key={'btcdirect'}
id={'BTC Direct'}
exchangeName={'btcdirect'}
deals={[{
fee: 2,
payment: 'card',
isFast: true,
isBest: false,
}, {
fee: 2,
payment: 'bank-transfer',
isFast: false,
isBest: false,
}]}
checked={selectedExchange === 'btcdirect'}
onChange={() => {
onSelectExchange('btcdirect');
}}
onClickInfoButton={() => setInfo(buildInfo({
exchangeName: 'btcdirect',
deals: [{
fee: 2,
payment: 'card',
isFast: true,
isBest: false,
}],
}))}
/>
)}
</div>
)}
{btcDirectOTCSupported?.success && btcDirectOTCSupported?.supported && (
Expand Down

0 comments on commit c41e672

Please sign in to comment.