decred.org/dcrdex@v1.0.5/client/core/notification.go (about) 1 // This code is available on the terms of the project LICENSE.md file, 2 // also available online at https://blueoakcouncil.org/license/1.0.0. 3 4 package core 5 6 import ( 7 "fmt" 8 "sync/atomic" 9 10 "decred.org/dcrdex/client/asset" 11 "decred.org/dcrdex/client/comms" 12 "decred.org/dcrdex/client/db" 13 "decred.org/dcrdex/dex" 14 "decred.org/dcrdex/dex/msgjson" 15 "decred.org/dcrdex/dex/order" 16 "decred.org/dcrdex/server/account" 17 ) 18 19 // Notifications should use the following note type strings. 20 const ( 21 NoteTypeFeePayment = "feepayment" 22 NoteTypeBondPost = "bondpost" 23 NoteTypeBondRefund = "bondrefund" 24 NoteTypeUnknownBond = "unknownbond" 25 NoteTypeSend = "send" 26 NoteTypeOrder = "order" 27 NoteTypeMatch = "match" 28 NoteTypeEpoch = "epoch" 29 NoteTypeConnEvent = "conn" 30 NoteTypeBalance = "balance" 31 NoteTypeSpots = "spots" 32 NoteTypeWalletConfig = "walletconfig" 33 NoteTypeWalletState = "walletstate" 34 NoteTypeWalletSync = "walletsync" 35 NoteTypeServerNotify = "notify" 36 NoteTypeSecurity = "security" 37 NoteTypeUpgrade = "upgrade" 38 NoteTypeBot = "bot" 39 NoteTypeDEXAuth = "dex_auth" 40 NoteTypeFiatRates = "fiatrateupdate" 41 NoteTypeCreateWallet = "createwallet" 42 NoteTypeLogin = "login" 43 NoteTypeWalletNote = "walletnote" 44 NoteTypeReputation = "reputation" 45 NoteTypeActionRequired = "actionrequired" 46 ) 47 48 var noteChanCounter uint64 49 50 func (c *Core) logNote(n Notification) { 51 // Do not log certain spammy note types that have no value in logs. 52 switch n.Type() { 53 case NoteTypeSpots: // expand this case as needed 54 return 55 default: 56 } 57 if n.Subject() == "" && n.Details() == "" { 58 return 59 } 60 61 logFun := c.log.Warnf // default in case the Severity level is unknown to notify 62 switch n.Severity() { 63 case db.Data: 64 logFun = c.log.Tracef 65 case db.Poke: 66 logFun = c.log.Debugf 67 case db.Success: 68 logFun = c.log.Infof 69 case db.WarningLevel: 70 logFun = c.log.Warnf 71 case db.ErrorLevel: 72 logFun = c.log.Errorf 73 } 74 75 logFun("notify: %v", n) 76 } 77 78 func (c *Core) Broadcast(n Notification) { 79 c.notify(n) 80 } 81 82 // notify sends a notification to all subscribers. If the notification is of 83 // sufficient severity, it is stored in the database. 84 func (c *Core) notify(n Notification) { 85 if n.Severity() >= db.Success { 86 c.db.SaveNotification(n.DBNote()) 87 } else if n.Severity() == db.Poke { 88 c.pokesCache.add(n.DBNote()) 89 } 90 91 c.logNote(n) 92 93 c.noteMtx.RLock() 94 for _, ch := range c.noteChans { 95 select { 96 case ch <- n: 97 default: 98 c.log.Errorf("blocking notification channel") 99 } 100 } 101 c.noteMtx.RUnlock() 102 } 103 104 // NoteFeed contains a receiving channel for notifications. 105 type NoteFeed struct { 106 C <-chan Notification 107 closer func() 108 } 109 110 // ReturnFeed should be called when the channel is no longer needed. 111 func (c *NoteFeed) ReturnFeed() { 112 if c.closer != nil { 113 c.closer() 114 } 115 } 116 117 // NotificationFeed returns a new receiving channel for notifications. The 118 // channel has capacity 1024, and should be monitored for the lifetime of the 119 // Core. Blocking channels are silently ignored. 120 func (c *Core) NotificationFeed() *NoteFeed { 121 id, ch := c.notificationFeed() 122 return &NoteFeed{ 123 C: ch, 124 closer: func() { c.returnFeed(id) }, 125 } 126 } 127 128 func (c *Core) notificationFeed() (uint64, <-chan Notification) { 129 ch := make(chan Notification, 1024) 130 cid := atomic.AddUint64(¬eChanCounter, 1) 131 c.noteMtx.Lock() 132 c.noteChans[cid] = ch 133 c.noteMtx.Unlock() 134 return cid, ch 135 } 136 137 func (c *Core) returnFeed(channelID uint64) { 138 c.noteMtx.Lock() 139 delete(c.noteChans, channelID) 140 c.noteMtx.Unlock() 141 } 142 143 // AckNotes sets the acknowledgement field for the notifications. 144 func (c *Core) AckNotes(ids []dex.Bytes) { 145 for _, id := range ids { 146 err := c.db.AckNotification(id) 147 if err != nil { 148 c.log.Errorf("error saving notification acknowledgement for %s: %v", id, err) 149 } 150 } 151 } 152 153 func (c *Core) formatDetails(topic Topic, args ...any) (translatedSubject, details string) { 154 locale := c.locale() 155 trans, found := locale.m[topic] 156 if !found { 157 c.log.Errorf("No translation found for topic %q", topic) 158 originTrans, found := originLocale[topic] 159 if !found { 160 return string(topic), "translation error" 161 } 162 return originTrans.subject.T, fmt.Sprintf(originTrans.template.T, args...) 163 } 164 return trans.subject.T, locale.printer.Sprintf(string(topic), args...) 165 } 166 167 func makeCoinIDToken(txHash string, assetID uint32) string { 168 return fmt.Sprintf("{{{%d|%s}}}", assetID, txHash) 169 } 170 171 func makeOrderToken(orderToken string) string { 172 return fmt.Sprintf("{{{order|%s}}}", orderToken) 173 } 174 175 // Notification is an interface for a user notification. Notification is 176 // satisfied by db.Notification, so concrete types can embed the db type. 177 type Notification interface { 178 // Type is a string ID unique to the concrete type. 179 Type() string 180 // Topic is a string ID unique to the message subject. Since subjects must 181 // be translated, we cannot rely on the subject to programmatically identify 182 // the message. 183 Topic() Topic 184 // Subject is a short description of the notification contents. When displayed 185 // to the user, the Subject will typically be given visual prominence. For 186 // notifications with Severity < Poke (not meant for display), the Subject 187 // field may be repurposed as a second-level category ID. 188 Subject() string 189 // Details should contain more detailed information. 190 Details() string 191 // Severity is the notification severity. 192 Severity() db.Severity 193 // Time is the notification timestamp. The timestamp is set in 194 // db.NewNotification. Time is a UNIX timestamp, in milliseconds. 195 Time() uint64 196 // Acked is true if the user has seen the notification. Acknowledgement is 197 // recorded with (*Core).AckNotes. 198 Acked() bool 199 // ID should be unique, except in the case of identical copies of 200 // db.Notification where the IDs should be the same. 201 ID() dex.Bytes 202 // Stamp sets the notification timestamp. If db.NewNotification is used to 203 // construct the db.Notification, the timestamp will already be set. 204 Stamp() 205 // DBNote returns the underlying *db.Notification. 206 DBNote() *db.Notification 207 // String generates a compact human-readable representation of the 208 // Notification that is suitable for logging. 209 String() string 210 } 211 212 // Topic is a language-independent unique ID for a Notification. 213 type Topic = db.Topic 214 215 // SecurityNote is a note regarding application security, credentials, or 216 // authentication. 217 type SecurityNote struct { 218 db.Notification 219 } 220 221 const ( 222 TopicSeedNeedsSaving Topic = "SeedNeedsSaving" 223 TopicUpgradedToSeed Topic = "UpgradedToSeed" 224 ) 225 226 func newSecurityNote(topic Topic, subject, details string, severity db.Severity) *SecurityNote { 227 return &SecurityNote{ 228 Notification: db.NewNotification(NoteTypeSecurity, topic, subject, details, severity), 229 } 230 } 231 232 const ( 233 TopicFeePaymentInProgress Topic = "FeePaymentInProgress" 234 TopicFeePaymentError Topic = "FeePaymentError" 235 TopicFeeCoinError Topic = "FeeCoinError" 236 TopicRegUpdate Topic = "RegUpdate" 237 TopicBondConfirming Topic = "BondConfirming" 238 TopicBondRefunded Topic = "BondRefunded" 239 TopicBondPostError Topic = "BondPostError" 240 TopicBondPostErrorConfirm Topic = "BondPostErrorConfirm" 241 TopicBondCoinError Topic = "BondCoinError" 242 TopicAccountRegistered Topic = "AccountRegistered" 243 TopicAccountUnlockError Topic = "AccountUnlockError" 244 TopicWalletConnectionWarning Topic = "WalletConnectionWarning" 245 TopicWalletUnlockError Topic = "WalletUnlockError" 246 TopicWalletCommsWarning Topic = "WalletCommsWarning" 247 TopicWalletPeersRestored Topic = "WalletPeersRestored" 248 ) 249 250 // FeePaymentNote is a notification regarding registration fee payment. 251 type FeePaymentNote struct { 252 db.Notification 253 Asset *uint32 `json:"asset,omitempty"` 254 Confirmations *uint32 `json:"confirmations,omitempty"` 255 Dex string `json:"dex,omitempty"` 256 } 257 258 func newFeePaymentNote(topic Topic, subject, details string, severity db.Severity, dexAddr string) *FeePaymentNote { 259 host, _ := addrHost(dexAddr) 260 return &FeePaymentNote{ 261 Notification: db.NewNotification(NoteTypeFeePayment, topic, subject, details, severity), 262 Dex: host, 263 } 264 } 265 266 func newFeePaymentNoteWithConfirmations(topic Topic, subject, details string, severity db.Severity, asset, currConfs uint32, dexAddr string) *FeePaymentNote { 267 feePmtNt := newFeePaymentNote(topic, subject, details, severity, dexAddr) 268 feePmtNt.Asset = &asset 269 feePmtNt.Confirmations = &currConfs 270 return feePmtNt 271 } 272 273 // BondRefundNote is a notification regarding bond refunds. 274 type BondRefundNote struct { 275 db.Notification 276 } 277 278 func newBondRefundNote(topic Topic, subject, details string, severity db.Severity) *BondRefundNote { 279 return &BondRefundNote{ 280 Notification: db.NewNotification(NoteTypeBondRefund, topic, subject, details, severity), 281 } 282 } 283 284 const ( 285 TopicBondAuthUpdate Topic = "BondAuthUpdate" 286 ) 287 288 // BondPostNote is a notification regarding bond posting. 289 type BondPostNote struct { 290 db.Notification 291 Asset *uint32 `json:"asset,omitempty"` 292 Confirmations *int32 `json:"confirmations,omitempty"` 293 BondedTier *int64 `json:"bondedTier,omitempty"` 294 CoinID *string `json:"coinID,omitempty"` 295 Dex string `json:"dex,omitempty"` 296 Auth *ExchangeAuth `json:"auth,omitempty"` 297 } 298 299 func newBondPostNote(topic Topic, subject, details string, severity db.Severity, dexAddr string) *BondPostNote { 300 host, _ := addrHost(dexAddr) 301 return &BondPostNote{ 302 Notification: db.NewNotification(NoteTypeBondPost, topic, subject, details, severity), 303 Dex: host, 304 } 305 } 306 307 func newBondPostNoteWithConfirmations( 308 topic Topic, 309 subject string, 310 details string, 311 severity db.Severity, 312 asset uint32, 313 coinID string, 314 currConfs int32, 315 host string, 316 auth *ExchangeAuth, 317 ) *BondPostNote { 318 319 bondPmtNt := newBondPostNote(topic, subject, details, severity, host) 320 bondPmtNt.Asset = &asset 321 bondPmtNt.CoinID = &coinID 322 bondPmtNt.Confirmations = &currConfs 323 bondPmtNt.Auth = auth 324 return bondPmtNt 325 } 326 327 func newBondPostNoteWithTier(topic Topic, subject, details string, severity db.Severity, dexAddr string, bondedTier int64, auth *ExchangeAuth) *BondPostNote { 328 bondPmtNt := newBondPostNote(topic, subject, details, severity, dexAddr) 329 bondPmtNt.BondedTier = &bondedTier 330 bondPmtNt.Auth = auth 331 return bondPmtNt 332 } 333 334 func newBondAuthUpdate(host string, auth *ExchangeAuth) *BondPostNote { 335 n := newBondPostNote(TopicBondAuthUpdate, "", "", db.Data, host) 336 n.Auth = auth 337 return n 338 } 339 340 // SendNote is a notification regarding a requested send or withdraw. 341 type SendNote struct { 342 db.Notification 343 } 344 345 const ( 346 TopicSendError Topic = "SendError" 347 TopicSendSuccess Topic = "SendSuccess" 348 ) 349 350 func newSendNote(topic Topic, subject, details string, severity db.Severity) *SendNote { 351 return &SendNote{ 352 Notification: db.NewNotification(NoteTypeSend, topic, subject, details, severity), 353 } 354 } 355 356 // OrderNote is a notification about an order or a match. 357 type OrderNote struct { 358 db.Notification 359 Order *Order `json:"order"` 360 TemporaryID uint64 `json:"tempID,omitempty"` 361 } 362 363 const ( 364 TopicOrderLoadFailure Topic = "OrderLoadFailure" 365 TopicOrderResumeFailure Topic = "OrderResumeFailure" 366 TopicBuyOrderPlaced Topic = "BuyOrderPlaced" 367 TopicSellOrderPlaced Topic = "SellOrderPlaced" 368 TopicYoloPlaced Topic = "YoloPlaced" 369 TopicMissingMatches Topic = "MissingMatches" 370 TopicWalletMissing Topic = "WalletMissing" 371 TopicMatchErrorCoin Topic = "MatchErrorCoin" 372 TopicMatchErrorContract Topic = "MatchErrorContract" 373 TopicMatchRecoveryError Topic = "MatchRecoveryError" 374 TopicOrderCoinError Topic = "OrderCoinError" 375 TopicOrderCoinFetchError Topic = "OrderCoinFetchError" 376 TopicPreimageSent Topic = "PreimageSent" 377 TopicCancelPreimageSent Topic = "CancelPreimageSent" 378 TopicMissedCancel Topic = "MissedCancel" 379 TopicOrderBooked Topic = "OrderBooked" 380 TopicNoMatch Topic = "NoMatch" 381 TopicBuyOrderCanceled Topic = "BuyOrderCanceled" 382 TopicSellOrderCanceled Topic = "SellOrderCanceled" 383 TopicCancel Topic = "Cancel" 384 TopicBuyMatchesMade Topic = "BuyMatchesMade" 385 TopicSellMatchesMade Topic = "SellMatchesMade" 386 TopicSwapSendError Topic = "SwapSendError" 387 TopicInitError Topic = "InitError" 388 TopicReportRedeemError Topic = "ReportRedeemError" 389 TopicSwapsInitiated Topic = "SwapsInitiated" 390 TopicRedemptionError Topic = "RedemptionError" 391 TopicMatchComplete Topic = "MatchComplete" 392 TopicRefundFailure Topic = "RefundFailure" 393 TopicMatchesRefunded Topic = "MatchesRefunded" 394 TopicMatchRevoked Topic = "MatchRevoked" 395 TopicOrderRevoked Topic = "OrderRevoked" 396 TopicOrderAutoRevoked Topic = "OrderAutoRevoked" 397 TopicMatchRecovered Topic = "MatchRecovered" 398 TopicCancellingOrder Topic = "CancellingOrder" 399 TopicOrderStatusUpdate Topic = "OrderStatusUpdate" 400 TopicMatchResolutionError Topic = "MatchResolutionError" 401 TopicFailedCancel Topic = "FailedCancel" 402 TopicOrderLoaded Topic = "OrderLoaded" 403 TopicOrderRetired Topic = "OrderRetired" 404 TopicAsyncOrderFailure Topic = "AsyncOrderFailure" 405 TopicAsyncOrderSubmitted Topic = "AsyncOrderSubmitted" 406 TopicOrderQuantityTooHigh Topic = "OrderQuantityTooHigh" 407 ) 408 409 func newOrderNote(topic Topic, subject, details string, severity db.Severity, corder *Order) *OrderNote { 410 return &OrderNote{ 411 Notification: db.NewNotification(NoteTypeOrder, topic, subject, details, severity), 412 Order: corder, 413 } 414 } 415 416 func newOrderNoteWithTempID(topic Topic, subject, details string, severity db.Severity, corder *Order, tempID uint64) *OrderNote { 417 note := newOrderNote(topic, subject, details, severity, corder) 418 note.TemporaryID = tempID 419 return note 420 } 421 422 // MatchNote is a notification about a match. 423 type MatchNote struct { 424 db.Notification 425 OrderID dex.Bytes `json:"orderID"` 426 Match *Match `json:"match"` 427 Host string `json:"host"` 428 MarketID string `json:"marketID"` 429 } 430 431 const ( 432 TopicAudit Topic = "Audit" 433 TopicAuditTrouble Topic = "AuditTrouble" 434 TopicNewMatch Topic = "NewMatch" 435 TopicCounterConfirms Topic = "CounterConfirms" 436 TopicConfirms Topic = "Confirms" 437 TopicRedemptionResubmitted Topic = "RedemptionResubmitted" 438 TopicSwapRefunded Topic = "SwapRefunded" 439 TopicRedemptionConfirmed Topic = "RedemptionConfirmed" 440 ) 441 442 func newMatchNote(topic Topic, subject, details string, severity db.Severity, t *trackedTrade, match *matchTracker) *MatchNote { 443 swapConfs, counterConfs := match.confirms() 444 if counterConfs < 0 { 445 // This can be -1 before it is actually checked, but for purposes of the 446 // match note, it should be non-negative. 447 counterConfs = 0 448 } 449 return &MatchNote{ 450 Notification: db.NewNotification(NoteTypeMatch, topic, subject, details, severity), 451 OrderID: t.ID().Bytes(), 452 Match: matchFromMetaMatchWithConfs(t.Order, &match.MetaMatch, swapConfs, 453 int64(t.metaData.FromSwapConf), counterConfs, int64(t.metaData.ToSwapConf), 454 int64(match.redemptionConfs), int64(match.redemptionConfsReq)), 455 Host: t.dc.acct.host, 456 MarketID: marketName(t.Base(), t.Quote()), 457 } 458 } 459 460 // String supplements db.Notification's Stringer with the Order's ID, if the 461 // Order is not nil. 462 func (on *OrderNote) String() string { 463 base := on.Notification.String() 464 if on.Order == nil { 465 return base 466 } 467 return fmt.Sprintf("%s - Order: %s", base, on.Order.ID) 468 } 469 470 // EpochNotification is a data notification that a new epoch has begun. 471 type EpochNotification struct { 472 db.Notification 473 Host string `json:"host"` 474 MarketID string `json:"marketID"` 475 Epoch uint64 `json:"epoch"` 476 } 477 478 const TopicEpoch Topic = "Epoch" 479 480 func newEpochNotification(host, mktID string, epochIdx uint64) *EpochNotification { 481 return &EpochNotification{ 482 Host: host, 483 MarketID: mktID, 484 Notification: db.NewNotification(NoteTypeEpoch, TopicEpoch, "", "", db.Data), 485 Epoch: epochIdx, 486 } 487 } 488 489 // String supplements db.Notification's Stringer with the Epoch index. 490 func (on *EpochNotification) String() string { 491 return fmt.Sprintf("%s - Index: %d", on.Notification.String(), on.Epoch) 492 } 493 494 // ConnEventNote is a notification regarding individual DEX connection status. 495 type ConnEventNote struct { 496 db.Notification 497 Host string `json:"host"` 498 ConnectionStatus comms.ConnectionStatus `json:"connectionStatus"` 499 } 500 501 const ( 502 TopicDEXConnected Topic = "DEXConnected" 503 TopicDEXDisconnected Topic = "DEXDisconnected" 504 TopicDexConnectivity Topic = "DEXConnectivity" 505 TopicDEXDisabled Topic = "DEXDisabled" 506 TopicDEXEnabled Topic = "DEXEnabled" 507 ) 508 509 func newConnEventNote(topic Topic, subject, host string, status comms.ConnectionStatus, details string, severity db.Severity) *ConnEventNote { 510 return &ConnEventNote{ 511 Notification: db.NewNotification(NoteTypeConnEvent, topic, subject, details, severity), 512 Host: host, 513 ConnectionStatus: status, 514 } 515 } 516 517 // FiatRatesNote is an update of fiat rate data for assets. 518 type FiatRatesNote struct { 519 db.Notification 520 FiatRates map[uint32]float64 `json:"fiatRates"` 521 } 522 523 const TopicFiatRatesUpdate Topic = "fiatrateupdate" 524 525 func newFiatRatesUpdate(rates map[uint32]float64) *FiatRatesNote { 526 return &FiatRatesNote{ 527 Notification: db.NewNotification(NoteTypeFiatRates, TopicFiatRatesUpdate, "", "", db.Data), 528 FiatRates: rates, 529 } 530 } 531 532 // BalanceNote is an update to a wallet's balance. 533 type BalanceNote struct { 534 db.Notification 535 AssetID uint32 `json:"assetID"` 536 Balance *WalletBalance `json:"balance"` 537 } 538 539 const TopicBalanceUpdated Topic = "BalanceUpdated" 540 541 func newBalanceNote(assetID uint32, bal *WalletBalance) *BalanceNote { 542 return &BalanceNote{ 543 Notification: db.NewNotification(NoteTypeBalance, TopicBalanceUpdated, "", "", db.Data), 544 AssetID: assetID, 545 Balance: bal, // Once created, balance is never modified by Core. 546 } 547 } 548 549 // SpotPriceNote is a notification of an update to the market's spot price. 550 type SpotPriceNote struct { 551 db.Notification 552 Host string `json:"host"` 553 Spots map[string]*msgjson.Spot `json:"spots"` 554 } 555 556 const TopicSpotsUpdate Topic = "SpotsUpdate" 557 558 func newSpotPriceNote(host string, spots map[string]*msgjson.Spot) *SpotPriceNote { 559 return &SpotPriceNote{ 560 Notification: db.NewNotification(NoteTypeSpots, TopicSpotsUpdate, "", "", db.Data), 561 Host: host, 562 Spots: spots, 563 } 564 } 565 566 // DEXAuthNote is a notification regarding individual DEX authentication status. 567 type DEXAuthNote struct { 568 db.Notification 569 Host string `json:"host"` 570 Authenticated bool `json:"authenticated"` 571 } 572 573 const ( 574 TopicDexAuthError Topic = "DexAuthError" 575 TopicDexAuthErrorBond Topic = "DexAuthErrorBond" 576 TopicUnknownOrders Topic = "UnknownOrders" 577 TopicOrdersReconciled Topic = "OrdersReconciled" 578 TopicBondConfirmed Topic = "BondConfirmed" 579 TopicBondExpired Topic = "BondExpired" 580 TopicAccountRegTier Topic = "AccountRegTier" 581 ) 582 583 func newDEXAuthNote(topic Topic, subject, host string, authenticated bool, details string, severity db.Severity) *DEXAuthNote { 584 return &DEXAuthNote{ 585 Notification: db.NewNotification(NoteTypeDEXAuth, topic, subject, details, severity), 586 Host: host, 587 Authenticated: authenticated, 588 } 589 } 590 591 // WalletConfigNote is a notification regarding a change in wallet 592 // configuration. 593 type WalletConfigNote struct { 594 db.Notification 595 Wallet *WalletState `json:"wallet"` 596 } 597 598 const ( 599 TopicWalletConfigurationUpdated Topic = "WalletConfigurationUpdated" 600 TopicWalletPasswordUpdated Topic = "WalletPasswordUpdated" 601 TopicWalletPeersWarning Topic = "WalletPeersWarning" 602 TopicWalletTypeDeprecated Topic = "WalletTypeDeprecated" 603 TopicWalletPeersUpdate Topic = "WalletPeersUpdate" 604 TopicBondWalletNotConnected Topic = "BondWalletNotConnected" 605 ) 606 607 func newWalletConfigNote(topic Topic, subject, details string, severity db.Severity, walletState *WalletState) *WalletConfigNote { 608 return &WalletConfigNote{ 609 Notification: db.NewNotification(NoteTypeWalletConfig, topic, subject, details, severity), 610 Wallet: walletState, 611 } 612 } 613 614 // WalletStateNote is a notification regarding a change in wallet state, 615 // including: creation, locking, unlocking, connect, disabling and enabling. This 616 // is intended to be a Data Severity notification. 617 type WalletStateNote WalletConfigNote 618 619 const TopicWalletState Topic = "WalletState" 620 const TopicTokenApproval Topic = "TokenApproval" 621 622 func newTokenApprovalNote(walletState *WalletState) *WalletStateNote { 623 return &WalletStateNote{ 624 Notification: db.NewNotification(NoteTypeWalletState, TopicTokenApproval, "", "", db.Data), 625 Wallet: walletState, 626 } 627 } 628 629 func newWalletStateNote(walletState *WalletState) *WalletStateNote { 630 return &WalletStateNote{ 631 Notification: db.NewNotification(NoteTypeWalletState, TopicWalletState, "", "", db.Data), 632 Wallet: walletState, 633 } 634 } 635 636 // WalletSyncNote is a notification of the wallet sync status. 637 type WalletSyncNote struct { 638 db.Notification 639 AssetID uint32 `json:"assetID"` 640 SyncStatus *asset.SyncStatus `json:"syncStatus"` 641 SyncProgress float32 `json:"syncProgress"` 642 } 643 644 const TopicWalletSync = "WalletSync" 645 646 func newWalletSyncNote(assetID uint32, ss *asset.SyncStatus) *WalletSyncNote { 647 return &WalletSyncNote{ 648 Notification: db.NewNotification(NoteTypeWalletSync, TopicWalletState, "", "", db.Data), 649 AssetID: assetID, 650 SyncStatus: ss, 651 SyncProgress: ss.BlockProgress(), 652 } 653 } 654 655 // ServerNotifyNote is a notification containing a server-originating message. 656 type ServerNotifyNote struct { 657 db.Notification 658 } 659 660 const ( 661 TopicMarketSuspendScheduled Topic = "MarketSuspendScheduled" 662 TopicMarketSuspended Topic = "MarketSuspended" 663 TopicMarketSuspendedWithPurge Topic = "MarketSuspendedWithPurge" 664 TopicMarketResumeScheduled Topic = "MarketResumeScheduled" 665 TopicMarketResumed Topic = "MarketResumed" 666 TopicPenalized Topic = "Penalized" 667 TopicDEXNotification Topic = "DEXNotification" 668 ) 669 670 func newServerNotifyNote(topic Topic, subject, details string, severity db.Severity) *ServerNotifyNote { 671 return &ServerNotifyNote{ 672 Notification: db.NewNotification(NoteTypeServerNotify, topic, subject, details, severity), 673 } 674 } 675 676 // UpgradeNote is a notification regarding an outdated client. 677 type UpgradeNote struct { 678 db.Notification 679 } 680 681 const ( 682 TopicUpgradeNeeded Topic = "UpgradeNeeded" 683 ) 684 685 func newUpgradeNote(topic Topic, subject, details string, severity db.Severity) *UpgradeNote { 686 return &UpgradeNote{ 687 Notification: db.NewNotification(NoteTypeUpgrade, topic, subject, details, severity), 688 } 689 } 690 691 // ServerConfigUpdateNote is sent when a server's configuration is updated. 692 type ServerConfigUpdateNote struct { 693 db.Notification 694 Host string `json:"host"` 695 } 696 697 const TopicServerConfigUpdate Topic = "ServerConfigUpdate" 698 699 func newServerConfigUpdateNote(host string) *ServerConfigUpdateNote { 700 return &ServerConfigUpdateNote{ 701 Notification: db.NewNotification(NoteTypeServerNotify, TopicServerConfigUpdate, "", "", db.Data), 702 Host: host, 703 } 704 } 705 706 // WalletCreationNote is a notification regarding asynchronous wallet creation. 707 type WalletCreationNote struct { 708 db.Notification 709 AssetID uint32 `json:"assetID"` 710 } 711 712 const ( 713 TopicQueuedCreationFailed Topic = "QueuedCreationFailed" 714 TopicQueuedCreationSuccess Topic = "QueuedCreationSuccess" 715 TopicCreationQueued Topic = "CreationQueued" 716 ) 717 718 func newWalletCreationNote(topic Topic, subject, details string, severity db.Severity, assetID uint32) *WalletCreationNote { 719 return &WalletCreationNote{ 720 Notification: db.NewNotification(NoteTypeCreateWallet, topic, subject, details, severity), 721 AssetID: assetID, 722 } 723 } 724 725 // LoginNote is a notification with the recent login status. 726 type LoginNote struct { 727 db.Notification 728 } 729 730 const TopicLoginStatus Topic = "LoginStatus" 731 732 func newLoginNote(message string) *LoginNote { 733 return &LoginNote{ 734 Notification: db.NewNotification(NoteTypeLogin, TopicLoginStatus, "", message, db.Data), 735 } 736 } 737 738 // WalletNote is a notification originating from a wallet. 739 type WalletNote struct { 740 db.Notification 741 Payload asset.WalletNotification `json:"payload"` 742 } 743 744 const TopicWalletNotification Topic = "WalletNotification" 745 746 func newWalletNote(n asset.WalletNotification) *WalletNote { 747 return &WalletNote{ 748 Notification: db.NewNotification(NoteTypeWalletNote, TopicWalletNotification, "", "", db.Data), 749 Payload: n, 750 } 751 } 752 753 type ReputationNote struct { 754 db.Notification 755 Host string `json:"host"` 756 Reputation account.Reputation `json:"rep"` 757 } 758 759 const TopicReputationUpdate = "ReputationUpdate" 760 761 func newReputationNote(host string, rep account.Reputation) *ReputationNote { 762 return &ReputationNote{ 763 Notification: db.NewNotification(NoteTypeReputation, TopicReputationUpdate, "", "", db.Data), 764 Host: host, 765 Reputation: rep, 766 } 767 } 768 769 const TopicUnknownBondTierZero = "UnknownBondTierZero" 770 771 // newUnknownBondTierZeroNote is used when unknown bonds are reported by the 772 // server while at target tier zero. 773 func newUnknownBondTierZeroNote(subject, details string) *db.Notification { 774 note := db.NewNotification(NoteTypeUnknownBond, TopicUnknownBondTierZero, subject, details, db.WarningLevel) 775 return ¬e 776 } 777 778 const ( 779 ActionIDRedeemRejected = "redeemRejected" 780 TopicRedeemRejected = "RedeemRejected" 781 ) 782 783 func newActionRequiredNote(actionID, uniqueID string, payload any) *asset.ActionRequiredNote { 784 n := &asset.ActionRequiredNote{ 785 UniqueID: uniqueID, 786 ActionID: actionID, 787 Payload: payload, 788 } 789 const routeNotNeededCuzCoreHasNoteType = "" 790 n.Route = routeNotNeededCuzCoreHasNoteType 791 return n 792 } 793 794 type RejectedRedemptionData struct { 795 OrderID dex.Bytes `json:"orderID"` 796 CoinID dex.Bytes `json:"coinID"` 797 AssetID uint32 `json:"assetID"` 798 CoinFmt string `json:"coinFmt"` 799 } 800 801 // ActionRequiredNote is structured like a WalletNote. The payload will be 802 // an *asset.ActionRequiredNote. This is done for compatibility reasons. 803 type ActionRequiredNote WalletNote 804 805 func newRejectedRedemptionNote(assetID uint32, oid order.OrderID, coinID []byte) (*asset.ActionRequiredNote, *ActionRequiredNote) { 806 data := &RejectedRedemptionData{ 807 AssetID: assetID, 808 OrderID: oid[:], 809 CoinID: coinID, 810 CoinFmt: coinIDString(assetID, coinID), 811 } 812 uniqueID := dex.Bytes(coinID).String() 813 actionNote := newActionRequiredNote(ActionIDRedeemRejected, uniqueID, data) 814 coreNote := &ActionRequiredNote{ 815 Notification: db.NewNotification(NoteTypeActionRequired, TopicRedeemRejected, "", "", db.Data), 816 Payload: actionNote, 817 } 818 return actionNote, coreNote 819 }