feat: Waku v2 bridge

Issue #12610
This commit is contained in:
Michal Iskierko
2023-11-12 13:29:38 +01:00
parent 56e7bd01ca
commit 6d31343205
6716 changed files with 1982502 additions and 5891 deletions
@@ -0,0 +1,151 @@
package currency
import (
"errors"
"math"
"strings"
iso4217 "github.com/ladydascalie/currency"
"github.com/status-im/status-go/services/wallet/market"
"github.com/status-im/status-go/services/wallet/token"
)
const decimalsCalculationCurrency = "USD"
const lowerTokenResolutionInUsd = 0.1
const higherTokenResolutionInUsd = 0.01
type Format struct {
Symbol string `json:"symbol"`
DisplayDecimals uint `json:"displayDecimals"`
StripTrailingZeroes bool `json:"stripTrailingZeroes"`
}
type FormatPerSymbol = map[string]Format
type Currency struct {
marketManager *market.Manager
}
func NewCurrency(marketManager *market.Manager) *Currency {
return &Currency{
marketManager: marketManager,
}
}
func IsCurrencyFiat(symbol string) bool {
return iso4217.Valid(strings.ToUpper(symbol))
}
func GetAllFiatCurrencySymbols() []string {
return iso4217.ValidCodes
}
func calculateFiatDisplayDecimals(symbol string) (uint, error) {
currency, err := iso4217.Get(strings.ToUpper(symbol))
if err != nil {
return 0, err
}
return uint(currency.MinorUnits()), nil
}
func calculateFiatCurrencyFormat(symbol string) (*Format, error) {
displayDecimals, err := calculateFiatDisplayDecimals(symbol)
if err != nil {
return nil, err
}
format := &Format{
Symbol: symbol,
DisplayDecimals: displayDecimals,
StripTrailingZeroes: false,
}
return format, nil
}
func calculateTokenDisplayDecimals(price float64) uint {
var displayDecimals float64 = 0.0
if price > 0 {
lowerDecimalsBound := math.Max(0.0, math.Log10(price)-math.Log10(lowerTokenResolutionInUsd))
upperDecimalsBound := math.Max(0.0, math.Log10(price)-math.Log10(higherTokenResolutionInUsd))
// Use as few decimals as needed to ensure lower precision
displayDecimals = math.Ceil(lowerDecimalsBound)
if displayDecimals+1.0 <= upperDecimalsBound {
// If allowed by upper bound, ensure resolution changes as soon as currency hits multiple of 10
displayDecimals += 1.0
}
}
return uint(displayDecimals)
}
func (cm *Currency) calculateTokenCurrencyFormat(symbol string, price float64) (*Format, error) {
pegSymbol := token.GetTokenPegSymbol(symbol)
if pegSymbol != "" {
var currencyFormat, err = calculateFiatCurrencyFormat(pegSymbol)
if err != nil {
return nil, err
}
currencyFormat.Symbol = symbol
return currencyFormat, nil
}
currencyFormat := &Format{
Symbol: symbol,
DisplayDecimals: calculateTokenDisplayDecimals(price),
StripTrailingZeroes: true,
}
return currencyFormat, nil
}
func GetFiatCurrencyFormats(symbols []string) (FormatPerSymbol, error) {
formats := make(FormatPerSymbol)
for _, symbol := range symbols {
format, err := calculateFiatCurrencyFormat(symbol)
if err != nil {
return nil, err
}
formats[symbol] = *format
}
return formats, nil
}
func (cm *Currency) FetchTokenCurrencyFormats(symbols []string) (FormatPerSymbol, error) {
formats := make(FormatPerSymbol)
// Get latest cached price, fetch only if not available
prices, err := cm.marketManager.GetOrFetchPrices(symbols, []string{decimalsCalculationCurrency}, math.MaxInt64)
if err != nil {
return nil, err
}
for _, symbol := range symbols {
priceData, ok := prices[symbol][decimalsCalculationCurrency]
if !ok {
return nil, errors.New("Could not get price for: " + symbol)
}
format, err := cm.calculateTokenCurrencyFormat(symbol, priceData.Price)
if err != nil {
return nil, err
}
formats[symbol] = *format
}
return formats, nil
}
@@ -0,0 +1,73 @@
package currency
import (
"context"
"database/sql"
)
type DB struct {
db *sql.DB
}
func NewCurrencyDB(sqlDb *sql.DB) *DB {
return &DB{
db: sqlDb,
}
}
func getCachedFormatsFromDBRows(rows *sql.Rows) (FormatPerSymbol, error) {
formats := make(FormatPerSymbol)
for rows.Next() {
var format Format
if err := rows.Scan(&format.Symbol, &format.DisplayDecimals, &format.StripTrailingZeroes); err != nil {
return nil, err
}
formats[format.Symbol] = format
}
return formats, nil
}
func (cdb *DB) GetCachedFormats() (FormatPerSymbol, error) {
rows, err := cdb.db.Query("SELECT symbol, display_decimals, strip_trailing_zeroes FROM currency_format_cache")
if err != nil {
return nil, err
}
defer rows.Close()
return getCachedFormatsFromDBRows(rows)
}
func (cdb *DB) UpdateCachedFormats(formats FormatPerSymbol) error {
tx, err := cdb.db.BeginTx(context.Background(), &sql.TxOptions{})
if err != nil {
return err
}
defer func() {
if err == nil {
err = tx.Commit()
return
}
// don't shadow original error
_ = tx.Rollback()
}()
insert, err := tx.Prepare(`INSERT OR REPLACE INTO currency_format_cache
(symbol, display_decimals, strip_trailing_zeroes)
VALUES
(?, ?, ?)`)
if err != nil {
return err
}
for _, format := range formats {
_, err = insert.Exec(format.Symbol, format.DisplayDecimals, format.StripTrailingZeroes)
if err != nil {
return err
}
}
return nil
}
@@ -0,0 +1,123 @@
package currency
import (
"context"
"database/sql"
"time"
"github.com/ethereum/go-ethereum/event"
"github.com/status-im/status-go/services/wallet/market"
"github.com/status-im/status-go/services/wallet/token"
"github.com/status-im/status-go/services/wallet/walletevent"
)
const (
EventCurrencyTickUpdateFormat walletevent.EventType = "wallet-currency-tick-update-format"
currencyFormatUpdateInterval = 1 * time.Hour
)
type Service struct {
currency *Currency
db *DB
tokenManager *token.Manager
walletFeed *event.Feed
cancelFn context.CancelFunc
}
func NewService(db *sql.DB, walletFeed *event.Feed, tokenManager *token.Manager, marketManager *market.Manager) *Service {
return &Service{
currency: NewCurrency(marketManager),
db: NewCurrencyDB(db),
tokenManager: tokenManager,
walletFeed: walletFeed,
}
}
func (s *Service) Start() {
// Update all fiat currency formats in cache
fiatFormats, err := s.getAllFiatCurrencyFormats()
if err == nil {
_ = s.db.UpdateCachedFormats(fiatFormats)
}
ctx, cancel := context.WithCancel(context.Background())
s.cancelFn = cancel
go func() {
ticker := time.NewTicker(currencyFormatUpdateInterval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
s.walletFeed.Send(walletevent.Event{
Type: EventCurrencyTickUpdateFormat,
})
}
}
}()
}
func (s *Service) Stop() {
if s.cancelFn != nil {
s.cancelFn()
}
}
func (s *Service) GetCachedCurrencyFormats() (FormatPerSymbol, error) {
return s.db.GetCachedFormats()
}
func (s *Service) FetchAllCurrencyFormats() (FormatPerSymbol, error) {
// Only token prices can change, so we fetch those
tokenFormats, err := s.fetchAllTokenCurrencyFormats()
if err != nil {
return nil, err
}
err = s.db.UpdateCachedFormats(tokenFormats)
if err != nil {
return nil, err
}
return s.GetCachedCurrencyFormats()
}
func (s *Service) getAllFiatCurrencyFormats() (FormatPerSymbol, error) {
return GetFiatCurrencyFormats(GetAllFiatCurrencySymbols())
}
func (s *Service) fetchAllTokenCurrencyFormats() (FormatPerSymbol, error) {
tokens, err := s.tokenManager.GetAllTokens()
if err != nil {
return nil, err
}
tokenPerSymbolMap := make(map[string]bool)
tokenSymbols := make([]string, 0)
for _, t := range tokens {
symbol := t.Symbol
if !tokenPerSymbolMap[symbol] {
tokenPerSymbolMap[symbol] = true
tokenSymbols = append(tokenSymbols, symbol)
}
}
tokenFormats, err := s.currency.FetchTokenCurrencyFormats(tokenSymbols)
if err != nil {
return nil, err
}
gweiSymbol := "Gwei"
tokenFormats[gweiSymbol] = Format{
Symbol: gweiSymbol,
DisplayDecimals: 9,
StripTrailingZeroes: true,
}
return tokenFormats, err
}