Skip to content

Commit

Permalink
reputation and limits ui
Browse files Browse the repository at this point in the history
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 committed Oct 23, 2023
1 parent b378082 commit d1658a6
Show file tree
Hide file tree
Showing 36 changed files with 1,302 additions and 586 deletions.
2 changes: 0 additions & 2 deletions client/app/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ type CoreConfig struct {
NoAutoWalletLock bool `long:"no-wallet-lock" description:"Disable locking of wallets on shutdown or logout. Use this if you want your external wallets to stay unlocked after closing the DEX app."`
NoAutoDBBackup bool `long:"no-db-backup" description:"Disable creation of a database backup on shutdown."`
UnlockCoinsOnLogin bool `long:"release-wallet-coins" description:"On login or wallet creation, instruct the wallet to release any coins that it may have locked."`
SimnetFiatRates bool `long:"simnet-fiat-rates" description:"Fetch fiat rates when running in simnet mode."`

ExtensionModeFile string `long:"extension-mode-file" description:"path to a file that specifies options for running core as an extension."`
}
Expand Down Expand Up @@ -203,7 +202,6 @@ func (cfg *Config) Core(log dex.Logger) *core.Config {
UnlockCoinsOnLogin: cfg.UnlockCoinsOnLogin,
NoAutoWalletLock: cfg.NoAutoWalletLock,
NoAutoDBBackup: cfg.NoAutoDBBackup,
SimnetFiatRates: cfg.SimnetFiatRates,
ExtensionModeFile: cfg.ExtensionModeFile,
}
}
Expand Down
25 changes: 18 additions & 7 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 @@ -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 @@ -1169,6 +1178,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 +1540,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 +1601,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 +1628,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 +1680,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
35 changes: 15 additions & 20 deletions 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 Expand Up @@ -1378,9 +1380,6 @@ type Config struct {
TorIsolation bool
// Language. A BCP 47 language tag. Default is en-US.
Language string
// SimnetFiatRates specifies whether to fetch fiat rates when running on
// simnet.
SimnetFiatRates bool

// NoAutoWalletLock instructs Core to skip locking the wallet on shutdown or
// logout. This can be helpful if the user wants the wallet to remain
Expand Down Expand Up @@ -1612,25 +1611,21 @@ func (c *Core) Run(ctx context.Context) {
c.latencyQ.Run(ctx)
}()

// Skip rate fetch setup if on simnet. Rate fetching maybe enabled if
// desired.
if c.cfg.Net != dex.Simnet || c.cfg.SimnetFiatRates {
// Retrieve disabled fiat rate sources from database.
disabledSources, err := c.db.DisabledRateSources()
if err != nil {
c.log.Errorf("Unable to retrieve disabled fiat rate source: %v", err)
}
// Retrieve disabled fiat rate sources from database.
disabledSources, err := c.db.DisabledRateSources()
if err != nil {
c.log.Errorf("Unable to retrieve disabled fiat rate source: %v", err)
}

// Construct enabled fiat rate sources.
fetchers:
for token, rateFetcher := range fiatRateFetchers {
for _, v := range disabledSources {
if token == v {
continue fetchers
}
// Construct enabled fiat rate sources.
fetchers:
for token, rateFetcher := range fiatRateFetchers {
for _, v := range disabledSources {
if token == v {
continue fetchers
}
c.fiatRateSources[token] = newCommonRateSource(rateFetcher)
}
c.fiatRateSources[token] = newCommonRateSource(rateFetcher)
}
c.fetchFiatExchangeRates(ctx)

Expand Down
3 changes: 2 additions & 1 deletion 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 @@ -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
4 changes: 3 additions & 1 deletion client/core/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,7 @@ type Market struct {
QuoteID uint32 `json:"quoteid"`
QuoteSymbol string `json:"quotesymbol"`
LotSize uint64 `json:"lotsize"`
ParcelSize uint32 `json:"parcelsize"`
RateStep uint64 `json:"ratestep"`
EpochLen uint64 `json:"epochlen"`
StartEpoch uint64 `json:"startepoch"`
Expand Down Expand Up @@ -693,7 +694,8 @@ type Exchange struct {
CandleDurs []string `json:"candleDurs"`
ViewOnly bool `json:"viewOnly"`
Auth ExchangeAuth `json:"auth"`
// TODO: Bonds slice(s) - and a LockedInBonds(assetID) method
PenaltyThreshold uint32 `json:"penaltyThreshold"`
MaxScore uint32 `json:"maxScore"`

// OLD fields for the legacy registration fee (V0PURGE):
RegFees map[string]*FeeAsset `json:"regFees"`
Expand Down
1 change: 0 additions & 1 deletion client/webserver/locales/ar.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,6 @@ var Ar = map[string]string{
"All markets at": "جميع الأسواق في",
"pick a different asset": "اختر أصلًا مختلفًا",
"Create": "انشاء",
"Register_loudly": "التسجيل!",
"1 Sync the Blockchain": "1: مزامنة سلسلة الكتل",
"Progress": "قيد التنفيذ",
"remaining": "الوقت المتبقي",
Expand Down
Loading

0 comments on commit d1658a6

Please sign in to comment.