Skip to content

Commit

Permalink
UI: catch trying to register with low balance
Browse files Browse the repository at this point in the history
This adds handling for insufficient balance and balance errors when registering.

The wallet's available balance is checked before confirming registration.
Then if there is a balance error on the backend anyway, the frontend recognizes
an error code and shows an appropriate error.
  • Loading branch information
vctt94 committed Jul 6, 2021
1 parent fcd3926 commit cb0e295
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 44 deletions.
2 changes: 1 addition & 1 deletion client/asset/dcr/dcr.go
Original file line number Diff line number Diff line change
Expand Up @@ -2421,7 +2421,7 @@ func (dcr *ExchangeWallet) sendRegFee(addr dcrutil.Address, regFee, netFeeRate u
}
coins, _, _, _, err := dcr.fund(enough)
if err != nil {
return nil, 0, fmt.Errorf("unable to pay registration fee of %s DCR with fee rate of %d atoms/byte: %w",
return nil, 0, fmt.Errorf("Unable to pay registration fee of %s DCR with fee rate of %d atoms/byte: %w",
amount(regFee), netFeeRate, err)
}
return dcr.sendCoins(addr, coins, regFee, netFeeRate, false)
Expand Down
6 changes: 6 additions & 0 deletions client/core/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"fmt"
)

// errors used on client/webserver/site/js/constants.js
// need to be careful for not going out of sync.
const (
walletErr = iota
walletAuthErr
Expand Down Expand Up @@ -53,6 +55,10 @@ func (e *Error) Error() string {
return e.s
}

func (e *Error) Code() *int {
return &e.code
}

// newError is a constructor for a new Error.
func newError(code int, s string, a ...interface{}) error {
return &Error{
Expand Down
83 changes: 45 additions & 38 deletions client/webserver/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package webserver

import (
"errors"
"fmt"
"net/http"
"time"
Expand All @@ -24,7 +25,7 @@ func (s *WebServer) apiGetFee(w http.ResponseWriter, r *http.Request) {
cert := []byte(form.Cert)
fee, err := s.core.GetFee(form.Addr, cert)
if err != nil {
s.writeAPIError(w, err.Error())
s.writeAPIError(w, err)
return
}
resp := struct {
Expand All @@ -46,7 +47,7 @@ func (s *WebServer) apiGetDEXInfo(w http.ResponseWriter, r *http.Request) {
cert := []byte(form.Cert)
exchangeInfo, err := s.core.GetDEXConfig(form.Addr, cert)
if err != nil {
s.writeAPIError(w, err.Error())
s.writeAPIError(w, err)
return
}
resp := struct {
Expand All @@ -69,7 +70,7 @@ func (s *WebServer) apiRegister(w http.ResponseWriter, r *http.Request) {
dcrID, _ := dex.BipSymbolID("dcr")
wallet := s.core.WalletState(dcrID)
if wallet == nil {
s.writeAPIError(w, "No Decred wallet")
s.writeAPIError(w, errors.New("No Decred wallet"))
return
}

Expand All @@ -80,7 +81,7 @@ func (s *WebServer) apiRegister(w http.ResponseWriter, r *http.Request) {
Fee: reg.Fee,
})
if err != nil {
s.writeAPIError(w, "registration error: %v", err)
s.writeAPIError(w, err)
return
}
// There was no error paying the fee, but we must wait on confirmations
Expand All @@ -101,7 +102,8 @@ func (s *WebServer) apiNewWallet(w http.ResponseWriter, r *http.Request) {
}
has := s.core.WalletState(form.AssetID) != nil
if has {
s.writeAPIError(w, "already have a wallet for %s", unbip(form.AssetID))

s.writeAPIError(w, fmt.Errorf("already have a wallet for %s", unbip(form.AssetID)))
return
}
// Wallet does not exist yet. Try to create it.
Expand All @@ -110,7 +112,7 @@ func (s *WebServer) apiNewWallet(w http.ResponseWriter, r *http.Request) {
Config: form.Config,
})
if err != nil {
s.writeAPIError(w, "error creating %s wallet: %v", unbip(form.AssetID), err)
s.writeAPIError(w, fmt.Errorf("error creating %s wallet: %w", unbip(form.AssetID), err))
return
}

Expand All @@ -127,12 +129,12 @@ func (s *WebServer) apiOpenWallet(w http.ResponseWriter, r *http.Request) {
}
status := s.core.WalletState(form.AssetID)
if status == nil {
s.writeAPIError(w, "No wallet for %d -> %s", form.AssetID, unbip(form.AssetID))
s.writeAPIError(w, fmt.Errorf("No wallet for %d -> %s", form.AssetID, unbip(form.AssetID)))
return
}
err := s.core.OpenWallet(form.AssetID, form.Pass)
if err != nil {
s.writeAPIError(w, "error unlocking %s wallet: %v", unbip(form.AssetID), err)
s.writeAPIError(w, fmt.Errorf("error unlocking %s wallet: %w", unbip(form.AssetID), err))
return
}

Expand All @@ -148,14 +150,14 @@ func (s *WebServer) apiNewDepositAddress(w http.ResponseWriter, r *http.Request)
return
}
if form.AssetID == nil {
s.writeAPIError(w, "missing asset ID")
s.writeAPIError(w, errors.New("missing asset ID"))
return
}
assetID := *form.AssetID

addr, err := s.core.NewDepositAddress(assetID)
if err != nil {
s.writeAPIError(w, "error connecting to %s wallet: %v", unbip(assetID), err)
s.writeAPIError(w, fmt.Errorf("error connecting to %s wallet: %w", unbip(assetID), err))
return
}

Expand All @@ -179,7 +181,7 @@ func (s *WebServer) apiConnectWallet(w http.ResponseWriter, r *http.Request) {
}
err := s.core.ConnectWallet(form.AssetID)
if err != nil {
s.writeAPIError(w, "error connecting to %s wallet: %v", unbip(form.AssetID), err)
s.writeAPIError(w, fmt.Errorf("error connecting to %s wallet: %v", unbip(form.AssetID), err))
return
}

Expand All @@ -196,7 +198,7 @@ func (s *WebServer) apiTrade(w http.ResponseWriter, r *http.Request) {
r.Close = true
ord, err := s.core.Trade(form.Pass, form.Order)
if err != nil {
s.writeAPIError(w, "error placing order: %v", err)
s.writeAPIError(w, fmt.Errorf("error placing order: %w", err))
return
}
resp := &struct {
Expand All @@ -220,7 +222,7 @@ func (s *WebServer) apiAccountExport(w http.ResponseWriter, r *http.Request) {
r.Close = true
account, err := s.core.AccountExport(form.Pass, form.Host)
if err != nil {
s.writeAPIError(w, "error exporting account: %v", err)
s.writeAPIError(w, fmt.Errorf("error exporting account: %w", err))
return
}
w.Header().Set("Connection", "close")
Expand All @@ -244,7 +246,7 @@ func (s *WebServer) apiAccountImport(w http.ResponseWriter, r *http.Request) {
r.Close = true
err := s.core.AccountImport(form.Pass, form.Account)
if err != nil {
s.writeAPIError(w, "error importing account: %v", err)
s.writeAPIError(w, fmt.Errorf("error importing account: %w", err))
return
}
w.Header().Set("Connection", "close")
Expand All @@ -262,7 +264,7 @@ func (s *WebServer) apiAccountDisable(w http.ResponseWriter, r *http.Request) {
// Disable account.
err := s.core.AccountDisable(form.Pass, form.Host)
if err != nil {
s.writeAPIError(w, "error disabling account: %v", err)
s.writeAPIError(w, fmt.Errorf("error disabling account: %w", err))
return
}
w.Header().Set("Connection", "close")
Expand All @@ -278,7 +280,7 @@ func (s *WebServer) apiCancel(w http.ResponseWriter, r *http.Request) {
}
err := s.core.Cancel(form.Pass, form.OrderID)
if err != nil {
s.writeAPIError(w, "error cancelling order %s: %v", form.OrderID, err)
s.writeAPIError(w, fmt.Errorf("error cancelling order %s: %w", form.OrderID, err))
return
}
writeJSON(w, simpleAck(), s.indent)
Expand All @@ -294,7 +296,7 @@ func (s *WebServer) apiCloseWallet(w http.ResponseWriter, r *http.Request) {
}
err := s.core.CloseWallet(form.AssetID)
if err != nil {
s.writeAPIError(w, "error locking %s wallet: %v", unbip(form.AssetID), err)
s.writeAPIError(w, fmt.Errorf("error locking %s wallet: %w", unbip(form.AssetID), err))
return
}

Expand All @@ -310,7 +312,7 @@ func (s *WebServer) apiInit(w http.ResponseWriter, r *http.Request) {
}
err := s.core.InitializeClient(login.Pass)
if err != nil {
s.writeAPIError(w, "initialization error: %v", err)
s.writeAPIError(w, fmt.Errorf("initialization error: %w", err))
return
}
s.actuallyLogin(w, r, login)
Expand All @@ -320,7 +322,7 @@ func (s *WebServer) apiInit(w http.ResponseWriter, r *http.Request) {
func (s *WebServer) apiIsInitialized(w http.ResponseWriter, r *http.Request) {
inited, err := s.core.IsInitialized()
if err != nil {
s.writeAPIError(w, "isinitialized error: %v", err)
s.writeAPIError(w, fmt.Errorf("isinitialized error: %w", err))
return
}
writeJSON(w, &struct {
Expand All @@ -346,7 +348,7 @@ func (s *WebServer) apiLogin(w http.ResponseWriter, r *http.Request) {
func (s *WebServer) apiLogout(w http.ResponseWriter, r *http.Request) {
err := s.core.Logout()
if err != nil {
s.writeAPIError(w, "logout error: %v", err)
s.writeAPIError(w, fmt.Errorf("logout error: %w", err))
return
}

Expand Down Expand Up @@ -380,7 +382,7 @@ func (s *WebServer) apiGetBalance(w http.ResponseWriter, r *http.Request) {
}
bal, err := s.core.AssetBalance(form.AssetID)
if err != nil {
s.writeAPIError(w, "balance error: %v", err)
s.writeAPIError(w, fmt.Errorf("balance error: %w", err))
return
}
resp := &struct {
Expand All @@ -404,7 +406,7 @@ func (s *WebServer) apiParseConfig(w http.ResponseWriter, r *http.Request) {
}
configMap, err := config.Parse([]byte(form.ConfigText))
if err != nil {
s.writeAPIError(w, "parse error: %v", err)
s.writeAPIError(w, fmt.Errorf("parse error: %w", err))
return
}
resp := &struct {
Expand All @@ -427,7 +429,7 @@ func (s *WebServer) apiWalletSettings(w http.ResponseWriter, r *http.Request) {
}
settings, err := s.core.WalletSettings(form.AssetID)
if err != nil {
s.writeAPIError(w, "error setting wallet settings: %v", err)
s.writeAPIError(w, fmt.Errorf("error setting wallet settings: %w", err))
return
}
writeJSON(w, &struct {
Expand All @@ -450,7 +452,7 @@ func (s *WebServer) apiDefaultWalletCfg(w http.ResponseWriter, r *http.Request)
}
cfg, err := s.core.AutoWalletConfig(form.AssetID)
if err != nil {
s.writeAPIError(w, "error getting wallet config: %v", err)
s.writeAPIError(w, fmt.Errorf("error getting wallet config: %w", err))
return
}
writeJSON(w, struct {
Expand All @@ -471,7 +473,7 @@ func (s *WebServer) apiOrders(w http.ResponseWriter, r *http.Request) {

ords, err := s.core.Orders(filter)
if err != nil {
s.writeAPIError(w, "Orders error: %v", err)
s.writeAPIError(w, fmt.Errorf("Orders error: %w", err))
return
}
writeJSON(w, &struct {
Expand All @@ -492,7 +494,7 @@ func (s *WebServer) apiOrder(w http.ResponseWriter, r *http.Request) {

ord, err := s.core.Order(oid)
if err != nil {
s.writeAPIError(w, "Order error: %v", err)
s.writeAPIError(w, fmt.Errorf("Order error: %w", err))
return
}
writeJSON(w, &struct {
Expand All @@ -519,7 +521,7 @@ func (s *WebServer) apiChangeAppPass(w http.ResponseWriter, r *http.Request) {
// Update application password.
err := s.core.ChangeAppPass(form.AppPW, form.NewAppPW)
if err != nil {
s.writeAPIError(w, "change app pass error: %v", err)
s.writeAPIError(w, fmt.Errorf("change app pass error: %w", err))
return
}

Expand Down Expand Up @@ -547,7 +549,7 @@ func (s *WebServer) apiReconfig(w http.ResponseWriter, r *http.Request) {
err := s.core.ReconfigureWallet(form.AppPW, form.NewWalletPW, form.AssetID,
form.Config)
if err != nil {
s.writeAPIError(w, "reconfig error: %v", err)
s.writeAPIError(w, fmt.Errorf("reconfig error: %w", err))
return
}

Expand All @@ -563,12 +565,12 @@ func (s *WebServer) apiWithdraw(w http.ResponseWriter, r *http.Request) {
}
state := s.core.WalletState(form.AssetID)
if state == nil {
s.writeAPIError(w, "no wallet found for %s", unbip(form.AssetID))
s.writeAPIError(w, fmt.Errorf("no wallet found for %s", unbip(form.AssetID)))
return
}
coin, err := s.core.Withdraw(form.Pass, form.AssetID, form.Value, form.Address)
if err != nil {
s.writeAPIError(w, "withdraw error: %v", err)
s.writeAPIError(w, fmt.Errorf("withdraw error: %w", err))
return
}
resp := struct {
Expand All @@ -594,7 +596,7 @@ func (s *WebServer) apiMaxBuy(w http.ResponseWriter, r *http.Request) {
}
maxBuy, err := s.core.MaxBuy(form.Host, form.Base, form.Quote, form.Rate)
if err != nil {
s.writeAPIError(w, "max order estimation error: %v", err)
s.writeAPIError(w, fmt.Errorf("max order estimation error: %w", err))
return
}
resp := struct {
Expand All @@ -619,7 +621,7 @@ func (s *WebServer) apiMaxSell(w http.ResponseWriter, r *http.Request) {
}
maxSell, err := s.core.MaxSell(form.Host, form.Base, form.Quote)
if err != nil {
s.writeAPIError(w, "max order estimation error: %v", err)
s.writeAPIError(w, fmt.Errorf("max order estimation error: %w", err))
return
}
resp := struct {
Expand All @@ -636,7 +638,7 @@ func (s *WebServer) apiMaxSell(w http.ResponseWriter, r *http.Request) {
func (s *WebServer) actuallyLogin(w http.ResponseWriter, r *http.Request, login *loginForm) {
loginResult, err := s.core.Login(login.Pass)
if err != nil {
s.writeAPIError(w, "login error: %v", err)
s.writeAPIError(w, fmt.Errorf("login error: %w", err))
return
}

Expand Down Expand Up @@ -681,12 +683,17 @@ func (s *WebServer) apiUser(w http.ResponseWriter, r *http.Request) {

// writeAPIError logs the formatted error and sends a standardResponse with the
// error message.
func (s *WebServer) writeAPIError(w http.ResponseWriter, format string, a ...interface{}) {
errMsg := fmt.Sprintf(format, a...)
log.Error(errMsg)
func (s *WebServer) writeAPIError(w http.ResponseWriter, err error) {
var cErr *core.Error
var code *int
if errors.As(err, &cErr) {
code = cErr.Code()
}
resp := &standardResponse{
OK: false,
Msg: errMsg,
OK: false,
Msg: err.Error(),
Code: code,
}
log.Error(err.Error())
writeJSON(w, resp, s.indent)
}
12 changes: 12 additions & 0 deletions client/webserver/site/src/html/register.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,18 @@
</div>
</form>

{{- /* registration failed low balance. */ -}}
<form class="card mx-auto my-5 bg1 d-hide" id="failedRegForm">
<div class="bg2 px-2 py-1 text-center fs18">Insuficient funds</div>
<div class="p-4">
<div class="fs16">
Registration failed. Look below for more details:
</div>
<div class="fs15 pt-3 text-center d-hide errcolor" id="regFundsErr"></div>
<hr class="dashed mt-4">
</div>
</form>

</div>
</div>
{{template "bottom"}}
Expand Down
5 changes: 5 additions & 0 deletions client/webserver/site/src/js/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Code errors from client/core/errors.go
// Need to be careful for not losing sync between them.
export const feeSendErr = 8

// end of errors.
Loading

0 comments on commit cb0e295

Please sign in to comment.