Skip to content

Commit

Permalink
ui: reputation and limits (#2575)
Browse files Browse the repository at this point in the history
* reputation and limits ui

Adapt registration forms to bonding. Show trading limits for the
tier input value. Add bond data to bond options registration form.
Reuse registration forms for updating bond options on dexsettings.
Show tier and reputation on dexsettings. Show parcel limits and
reputation on markets view.

A couple of notes. Nowhere are we going to show the cost of an
individual bond, because that value is misleading. Instead, we'll
show the "bond lock", which is the amount that will actually be
locked for the given tier.

I have removed the simnetfiatrates settings. fiat rates are all
on by default. I have also changed coinpaprika so that it pulls
all coin data, instead of just the ones with wallets. This enables
us to e.g. show USD conversion of the bond lock value for assets
that we don't yet have a wallet for.
  • Loading branch information
buck54321 authored Nov 17, 2023
1 parent e8c529f commit b6c4c3e
Show file tree
Hide file tree
Showing 38 changed files with 1,593 additions and 651 deletions.
34 changes: 24 additions & 10 deletions client/core/bond.go
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,10 @@ func (c *Core) bondStateOfDEX(dc *dexConnection, bondCfg *dexBondCfg) *dexAcctBo
return state
}

func (c *Core) exchangeAuth(dc *dexConnection) *ExchangeAuth {
return &c.bondStateOfDEX(dc, c.dexBondConfig(dc, time.Now().Unix())).ExchangeAuth
}

type bondID struct {
assetID uint32
coinID []byte
Expand Down Expand Up @@ -908,7 +912,7 @@ func (c *Core) monitorBondConfs(dc *dexConnection, bond *asset.Bond, reqConfs ui
if confs < reqConfs {
details := fmt.Sprintf("Bond confirmations %v/%v", confs, reqConfs)
c.notify(newBondPostNoteWithConfirmations(TopicRegUpdate, string(TopicRegUpdate),
details, db.Data, assetID, coinIDStr, int32(confs), host))
details, db.Data, assetID, coinIDStr, int32(confs), host, c.exchangeAuth(dc)))
}

return confs >= reqConfs, nil
Expand Down Expand Up @@ -982,12 +986,12 @@ func (c *Core) nextBondKey(assetID uint32) (*secp256k1.PrivateKey, uint32, error
// the target trading tier, the preferred asset to use for bonds, and the
// maximum amount allowable to be locked in bonds.
func (c *Core) UpdateBondOptions(form *BondOptionsForm) error {
dc, _, err := c.dex(form.Addr)
dc, _, err := c.dex(form.Host)
if err != nil {
return err
}
// TODO: exclude unregistered and/or watch-only
dbAcct, err := c.db.Account(form.Addr)
dbAcct, err := c.db.Account(form.Host)
if err != nil {
return err
}
Expand Down Expand Up @@ -1015,8 +1019,14 @@ func (c *Core) UpdateBondOptions(form *BondOptionsForm) error {
}
}()

var success bool
dc.acct.authMtx.Lock()
defer dc.acct.authMtx.Unlock()
defer func() {
dc.acct.authMtx.Unlock()
if success {
c.notify(newBondAuthUpdate(dc.acct.host, c.exchangeAuth(dc)))
}
}()

if !dc.acct.isAuthed {
return errors.New("login or register first")
Expand All @@ -1025,7 +1035,6 @@ func (c *Core) UpdateBondOptions(form *BondOptionsForm) error {
// Revert to initial values if we encounter any error below.
bondAssetID0 = dc.acct.bondAsset
targetTier0, maxBondedAmt0, penaltyComps0 = dc.acct.targetTier, dc.acct.maxBondedAmt, dc.acct.penaltyComps
var success bool
defer func() { // still under authMtx lock on defer stack
if !success {
dc.acct.bondAsset = bondAssetID0
Expand Down Expand Up @@ -1054,7 +1063,10 @@ func (c *Core) UpdateBondOptions(form *BondOptionsForm) error {
dbAcct.TargetTier = targetTier
}

penaltyComps := form.PenaltyComps
var penaltyComps = penaltyComps0
if form.PenaltyComps != nil {
penaltyComps = *form.PenaltyComps
}
dc.acct.penaltyComps = penaltyComps
dbAcct.PenaltyComps = penaltyComps

Expand Down Expand Up @@ -1169,6 +1181,7 @@ func (c *Core) UpdateBondOptions(form *BondOptionsForm) error {
success = true
} // else we might have already done ReserveBondFunds...
return err

}

// BondsFeeBuffer suggests how much extra may be required for the transaction
Expand Down Expand Up @@ -1530,7 +1543,7 @@ func (c *Core) makeAndPostBond(dc *dexConnection, acctExists bool, wallet *xcWal
details := fmt.Sprintf("Waiting for %d confirmations to post bond %v (%s) to %s",
reqConfs, bondCoinStr, unbip(bond.AssetID), dc.acct.host) // TODO: subject, detail := c.formatDetails(...)
c.notify(newBondPostNoteWithConfirmations(TopicBondConfirming, string(TopicBondConfirming),
details, db.Success, bond.AssetID, bondCoinStr, 0, dc.acct.host))
details, db.Success, bond.AssetID, bondCoinStr, 0, dc.acct.host, c.exchangeAuth(dc)))
// Set up the coin waiter, which watches confirmations so the user knows
// when to expect their account to be marked paid by the server.
c.monitorBondConfs(dc, bond, reqConfs)
Expand Down Expand Up @@ -1591,7 +1604,8 @@ func (c *Core) bondConfirmed(dc *dexConnection, assetID uint32, coinID []byte, p
}
c.log.Infof("Bond %s (%s) confirmed.", bondIDStr, unbip(assetID))
details := fmt.Sprintf("New tier = %d (target = %d).", effectiveTier, targetTier) // TODO: format to subject,details
c.notify(newBondPostNoteWithTier(TopicBondConfirmed, string(TopicBondConfirmed), details, db.Success, dc.acct.host, bondedTier))

c.notify(newBondPostNoteWithTier(TopicBondConfirmed, string(TopicBondConfirmed), details, db.Success, dc.acct.host, bondedTier, c.exchangeAuth(dc)))
} else if !foundConfirmed {
c.log.Errorf("bondConfirmed: Bond %s (%s) not found", bondIDStr, unbip(assetID))
// just try to authenticate...
Expand All @@ -1617,7 +1631,7 @@ func (c *Core) bondConfirmed(dc *dexConnection, assetID uint32, coinID []byte, p

details := fmt.Sprintf("New tier = %d", effectiveTier) // TODO: format to subject,details
c.notify(newBondPostNoteWithTier(TopicAccountRegistered, string(TopicAccountRegistered),
details, db.Success, dc.acct.host, bondedTier)) // possibly redundant with SubjectBondConfirmed
details, db.Success, dc.acct.host, bondedTier, c.exchangeAuth(dc))) // possibly redundant with SubjectBondConfirmed

return nil
}
Expand Down Expand Up @@ -1669,7 +1683,7 @@ func (c *Core) bondExpired(dc *dexConnection, assetID uint32, coinID []byte, not
if int64(targetTier) > effectiveTier {
details := fmt.Sprintf("New tier = %d (target = %d).", effectiveTier, targetTier)
c.notify(newBondPostNoteWithTier(TopicBondExpired, string(TopicBondExpired),
details, db.WarningLevel, dc.acct.host, bondedTier))
details, db.WarningLevel, dc.acct.host, bondedTier, c.exchangeAuth(dc)))
}

return nil
Expand Down
4 changes: 3 additions & 1 deletion client/core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@ func coreMarketFromMsgMarket(dc *dexConnection, msgMkt *msgjson.Market) *Market
QuoteID: quote.ID,
QuoteSymbol: quote.Symbol,
LotSize: msgMkt.LotSize,
ParcelSize: msgMkt.ParcelSize,
RateStep: msgMkt.RateStep,
EpochLen: msgMkt.EpochLen,
StartEpoch: msgMkt.StartEpoch,
Expand Down Expand Up @@ -519,7 +520,8 @@ func (c *Core) exchangeInfo(dc *dexConnection) *Exchange {
CandleDurs: cfg.BinSizes,
ViewOnly: dc.acct.isViewOnly(),
Auth: acctBondState.ExchangeAuth,
// TODO: Bonds
MaxScore: cfg.MaxScore,
PenaltyThreshold: cfg.PenaltyThreshold,

// Legacy reg fee (V0PURGE)
RegFees: feeAssets,
Expand Down
15 changes: 8 additions & 7 deletions client/core/core_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ func testDexConnection(ctx context.Context, crypter *tCrypter) (*dexConnection,
Base: tUTXOAssetA.ID,
Quote: tUTXOAssetB.ID,
LotSize: dcrBtcLotSize,
ParcelSize: 100,
RateStep: dcrBtcRateStep,
EpochLen: 60000,
MarketBuyBuffer: 1.1,
Expand Down Expand Up @@ -10575,7 +10576,7 @@ func TestUpdateBondOptions(t *testing.T) {
name: "set target tier to 1",
bal: singlyBondedReserves,
form: BondOptionsForm{
Addr: acct.host,
Host: acct.host,
TargetTier: &targetTier,
BondAssetID: &bondAsset.ID,
},
Expand All @@ -10589,7 +10590,7 @@ func TestUpdateBondOptions(t *testing.T) {
name: "low balance",
bal: singlyBondedReserves - 1,
form: BondOptionsForm{
Addr: acct.host,
Host: acct.host,
TargetTier: &targetTier,
BondAssetID: &bondAsset.ID,
},
Expand All @@ -10599,7 +10600,7 @@ func TestUpdateBondOptions(t *testing.T) {
name: "max-bonded too low",
bal: singlyBondedReserves,
form: BondOptionsForm{
Addr: acct.host,
Host: acct.host,
TargetTier: &targetTier,
BondAssetID: &bondAsset.ID,
MaxBondedAmt: &tooLowMaxBonded,
Expand All @@ -10609,7 +10610,7 @@ func TestUpdateBondOptions(t *testing.T) {
{
name: "unsupported bond asset",
form: BondOptionsForm{
Addr: acct.host,
Host: acct.host,
TargetTier: &targetTier,
BondAssetID: &wrongBondAssetID,
},
Expand All @@ -10619,7 +10620,7 @@ func TestUpdateBondOptions(t *testing.T) {
name: "lower target tier with zero balance OK",
bal: 0,
form: BondOptionsForm{
Addr: acct.host,
Host: acct.host,
TargetTier: &targetTierZero,
BondAssetID: &bondAsset.ID,
},
Expand All @@ -10634,7 +10635,7 @@ func TestUpdateBondOptions(t *testing.T) {
name: "lower target tier to zero with other exchanges still keeps reserves",
bal: 0,
form: BondOptionsForm{
Addr: acct.host,
Host: acct.host,
TargetTier: &targetTierZero,
BondAssetID: &bondAsset.ID,
},
Expand Down Expand Up @@ -10775,7 +10776,7 @@ func TestRotateBonds(t *testing.T) {
// if the locktime is not too soon.
acct.bonds = append(acct.bonds, acct.pendingBonds[0])
acct.pendingBonds = nil
acct.bonds[0].LockTime = mergeableLocktimeThresh + 1
acct.bonds[0].LockTime = mergeableLocktimeThresh + 5
rig.queuePrevalidateBond()
run(1, 0, 2*bondAsset.Amt+bondFeeBuffer)
mergingBond := acct.pendingBonds[0]
Expand Down
64 changes: 33 additions & 31 deletions client/core/exchangeratefetcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const (
var (
dcrDataURL = "https://explorer.dcrdata.org/api/exchangerate"
// coinpaprika has two options. /tickers is for the top 2500 assets all in
// one request. /ticker/[slug] is for a single ticker. From testing
// one request. /tickers/[slug] is for a single ticker. From testing
// Single ticker request took 274.626125ms
// Size of single ticker response: 0.733 kB
// All tickers request took 47.651851ms
Expand All @@ -48,7 +48,7 @@ var (
// So any more than 25000 / 3600 = 6.9 assets, and we can expect to run into
// rate limits. But the bandwidth of the full tickers request is kinda
// ridiculous too. Solution needed.
coinpaprikaURL = "https://api.coinpaprika.com/v1/tickers/%s"
coinpaprikaURL = "https://api.coinpaprika.com/v1/tickers"
// The best info I can find on Messari says
// Without an API key requests are rate limited to 20 requests per minute
// and 1000 requests per day.
Expand Down Expand Up @@ -142,26 +142,12 @@ func newCommonRateSource(fetcher rateFetcher) *commonRateSource {
// for sample request and response information.
func FetchCoinpaprikaRates(ctx context.Context, log dex.Logger, assets map[uint32]*SupportedAsset) map[uint32]float64 {
fiatRates := make(map[uint32]float64)
fetchRate := func(sa *SupportedAsset) {
assetID := sa.ID
if sa.Wallet == nil {
// we don't want to fetch rates for assets with no wallet.
return
}

res := new(struct {
Quotes struct {
Currency struct {
Price float64 `json:"price"`
} `json:"USD"`
} `json:"quotes"`
})

slugAssets := make(map[string]uint32)
for _, sa := range assets {
symbol := dex.TokenSymbol(sa.Symbol)
if symbol == "dextt" {
return
continue
}

name := sa.Name
// TODO: Store these within the *SupportedAsset.
switch symbol {
Expand All @@ -171,21 +157,37 @@ func FetchCoinpaprikaRates(ctx context.Context, log dex.Logger, assets map[uint3
symbol = "matic"
name = "polygon"
}
slug := coinpapSlug(symbol, name)
slugAssets[slug] = sa.ID
}

reqStr := fmt.Sprintf(coinpaprikaURL, coinpapSlug(symbol, name))

ctx, cancel := context.WithTimeout(ctx, fiatRequestTimeout)
defer cancel()
ctx, cancel := context.WithTimeout(ctx, fiatRequestTimeout)
defer cancel()

if err := getRates(ctx, reqStr, res); err != nil {
log.Errorf("Error getting fiat exchange rates from coinpaprika: %v", err)
return
}
var res []*struct {
ID string `json:"id"`
Quotes struct {
USD struct {
Price float64 `json:"price"`
} `json:"USD"`
} `json:"quotes"`
}

fiatRates[assetID] = res.Quotes.Currency.Price
if err := getRates(ctx, coinpaprikaURL, &res); err != nil {
log.Errorf("Error getting fiat exchange rates from coinpaprika: %v", err)
return fiatRates
}
for _, sa := range assets {
fetchRate(sa)
for _, coinInfo := range res {
assetID, found := slugAssets[coinInfo.ID]
if !found {
continue
}
price := coinInfo.Quotes.USD.Price
if price == 0 {
log.Errorf("zero-price returned from coinpaprika for slug %s", coinInfo.ID)
continue
}
fiatRates[assetID] = price
}
return fiatRates
}
Expand Down Expand Up @@ -288,6 +290,6 @@ func getRates(ctx context.Context, url string, thing any) error {
return fmt.Errorf("error %d fetching %q", resp.StatusCode, url)
}

reader := io.LimitReader(resp.Body, 1<<20)
reader := io.LimitReader(resp.Body, 1<<22)
return json.NewDecoder(reader).Decode(thing)
}
44 changes: 34 additions & 10 deletions client/core/notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,14 +265,19 @@ func newBondRefundNote(topic Topic, subject, details string, severity db.Severit
}
}

const (
TopicBondAuthUpdate Topic = "BondAuthUpdate"
)

// BondPostNote is a notification regarding bond posting.
type BondPostNote struct {
db.Notification
Asset *uint32 `json:"asset,omitempty"`
Confirmations *int32 `json:"confirmations,omitempty"`
BondedTier *int64 `json:"bondedTier,omitempty"`
CoinID *string `json:"coinID,omitempty"`
Dex string `json:"dex,omitempty"`
Asset *uint32 `json:"asset,omitempty"`
Confirmations *int32 `json:"confirmations,omitempty"`
BondedTier *int64 `json:"bondedTier,omitempty"`
CoinID *string `json:"coinID,omitempty"`
Dex string `json:"dex,omitempty"`
Auth *ExchangeAuth `json:"auth,omitempty"`
}

func newBondPostNote(topic Topic, subject, details string, severity db.Severity, dexAddr string) *BondPostNote {
Expand All @@ -283,20 +288,39 @@ func newBondPostNote(topic Topic, subject, details string, severity db.Severity,
}
}

func newBondPostNoteWithConfirmations(topic Topic, subject, details string, severity db.Severity, asset uint32, coinID string, currConfs int32, dexAddr string) *BondPostNote {
bondPmtNt := newBondPostNote(topic, subject, details, severity, dexAddr)
func newBondPostNoteWithConfirmations(
topic Topic,
subject string,
details string,
severity db.Severity,
asset uint32,
coinID string,
currConfs int32,
host string,
auth *ExchangeAuth,
) *BondPostNote {

bondPmtNt := newBondPostNote(topic, subject, details, severity, host)
bondPmtNt.Asset = &asset
bondPmtNt.CoinID = &coinID
bondPmtNt.Confirmations = &currConfs
bondPmtNt.Auth = auth
return bondPmtNt
}

func newBondPostNoteWithTier(topic Topic, subject, details string, severity db.Severity, dexAddr string, bondedTier int64) *BondPostNote {
func newBondPostNoteWithTier(topic Topic, subject, details string, severity db.Severity, dexAddr string, bondedTier int64, auth *ExchangeAuth) *BondPostNote {
bondPmtNt := newBondPostNote(topic, subject, details, severity, dexAddr)
bondPmtNt.BondedTier = &bondedTier
bondPmtNt.Auth = auth
return bondPmtNt
}

func newBondAuthUpdate(host string, auth *ExchangeAuth) *BondPostNote {
n := newBondPostNote(TopicBondAuthUpdate, "", "", db.Data, host)
n.Auth = auth
return n
}

// SendNote is a notification regarding a requested send or withdraw.
type SendNote struct {
db.Notification
Expand Down Expand Up @@ -673,8 +697,8 @@ func newWalletNote(n asset.WalletNotification) *WalletNote {

type ReputationNote struct {
db.Notification
Host string
Reputation account.Reputation
Host string `json:"host"`
Reputation account.Reputation `json:"rep"`
}

const TopicReputationUpdate = "ReputationUpdate"
Expand Down
Loading

0 comments on commit b6c4c3e

Please sign in to comment.