228 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			228 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package store
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"fmt"
 | 
						|
	"math/big"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"git.defalsify.org/vise.git/logging"
 | 
						|
	storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
 | 
						|
	dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	logg = logging.NewVanilla().WithDomain("vouchers").WithContextKey("SessionId")
 | 
						|
)
 | 
						|
 | 
						|
// symbolReplacements holds mappings of invalid symbols → valid ones
 | 
						|
var symbolReplacements = map[string]string{
 | 
						|
	"USD₮": "USDT",
 | 
						|
}
 | 
						|
 | 
						|
// VoucherMetadata helps organize data fields
 | 
						|
type VoucherMetadata struct {
 | 
						|
	Symbols   string
 | 
						|
	Balances  string
 | 
						|
	Decimals  string
 | 
						|
	Addresses string
 | 
						|
}
 | 
						|
 | 
						|
// sanitizeSymbol replaces known invalid token symbols with normalized ones
 | 
						|
func sanitizeSymbol(symbol string) string {
 | 
						|
	if replacement, ok := symbolReplacements[symbol]; ok {
 | 
						|
		return replacement
 | 
						|
	}
 | 
						|
	return symbol
 | 
						|
}
 | 
						|
 | 
						|
// ProcessVouchers converts holdings into formatted strings
 | 
						|
func ProcessVouchers(holdings []dataserviceapi.TokenHoldings) VoucherMetadata {
 | 
						|
	var data VoucherMetadata
 | 
						|
	var symbols, balances, decimals, addresses []string
 | 
						|
 | 
						|
	for i, h := range holdings {
 | 
						|
		// normalize token symbol before use
 | 
						|
		cleanSymbol := sanitizeSymbol(h.TokenSymbol)
 | 
						|
 | 
						|
		symbols = append(symbols, fmt.Sprintf("%d:%s", i+1, cleanSymbol))
 | 
						|
 | 
						|
		// Scale down the balance
 | 
						|
		scaledBalance := ScaleDownBalance(h.Balance, h.TokenDecimals)
 | 
						|
 | 
						|
		balances = append(balances, fmt.Sprintf("%d:%s", i+1, scaledBalance))
 | 
						|
		decimals = append(decimals, fmt.Sprintf("%d:%s", i+1, h.TokenDecimals))
 | 
						|
		addresses = append(addresses, fmt.Sprintf("%d:%s", i+1, h.TokenAddress))
 | 
						|
	}
 | 
						|
 | 
						|
	data.Symbols = strings.Join(symbols, "\n")
 | 
						|
	data.Balances = strings.Join(balances, "\n")
 | 
						|
	data.Decimals = strings.Join(decimals, "\n")
 | 
						|
	data.Addresses = strings.Join(addresses, "\n")
 | 
						|
 | 
						|
	return data
 | 
						|
}
 | 
						|
 | 
						|
func ScaleDownBalance(balance, decimals string) string {
 | 
						|
	// Convert balance and decimals to big.Float
 | 
						|
	bal := new(big.Float)
 | 
						|
	bal.SetString(balance)
 | 
						|
 | 
						|
	dec, ok := new(big.Int).SetString(decimals, 10)
 | 
						|
	if !ok {
 | 
						|
		dec = big.NewInt(0) // Default to 0 decimals in case of conversion failure
 | 
						|
	}
 | 
						|
 | 
						|
	divisor := new(big.Float).SetInt(new(big.Int).Exp(big.NewInt(10), dec, nil))
 | 
						|
	scaledBalance := new(big.Float).Quo(bal, divisor)
 | 
						|
 | 
						|
	// Return the scaled balance without trailing decimals if it's an integer
 | 
						|
	if scaledBalance.IsInt() {
 | 
						|
		return scaledBalance.Text('f', 0)
 | 
						|
	}
 | 
						|
	return scaledBalance.Text('f', -1)
 | 
						|
}
 | 
						|
 | 
						|
// GetVoucherData retrieves and matches voucher data
 | 
						|
func GetVoucherData(ctx context.Context, store DataStore, sessionId string, input string) (*dataserviceapi.TokenHoldings, error) {
 | 
						|
	keys := []storedb.DataTyp{
 | 
						|
		storedb.DATA_VOUCHER_SYMBOLS,
 | 
						|
		storedb.DATA_VOUCHER_BALANCES,
 | 
						|
		storedb.DATA_VOUCHER_DECIMALS,
 | 
						|
		storedb.DATA_VOUCHER_ADDRESSES,
 | 
						|
	}
 | 
						|
	data := make(map[storedb.DataTyp]string)
 | 
						|
 | 
						|
	for _, key := range keys {
 | 
						|
		value, err := store.ReadEntry(ctx, sessionId, key)
 | 
						|
		if err != nil {
 | 
						|
			return nil, fmt.Errorf("failed to get data key %x: %v", key, err)
 | 
						|
		}
 | 
						|
		data[key] = string(value)
 | 
						|
	}
 | 
						|
 | 
						|
	symbol, balance, decimal, address := MatchVoucher(input,
 | 
						|
		data[storedb.DATA_VOUCHER_SYMBOLS],
 | 
						|
		data[storedb.DATA_VOUCHER_BALANCES],
 | 
						|
		data[storedb.DATA_VOUCHER_DECIMALS],
 | 
						|
		data[storedb.DATA_VOUCHER_ADDRESSES],
 | 
						|
	)
 | 
						|
 | 
						|
	if symbol == "" {
 | 
						|
		return nil, nil
 | 
						|
	}
 | 
						|
 | 
						|
	return &dataserviceapi.TokenHoldings{
 | 
						|
		TokenSymbol:   string(symbol),
 | 
						|
		Balance:       string(balance),
 | 
						|
		TokenDecimals: string(decimal),
 | 
						|
		TokenAddress:  string(address),
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
// MatchVoucher finds the matching voucher symbol, balance, decimals and contract address based on the input.
 | 
						|
func MatchVoucher(input, symbols, balances, decimals, addresses string) (symbol, balance, decimal, address string) {
 | 
						|
	symList := strings.Split(symbols, "\n")
 | 
						|
	balList := strings.Split(balances, "\n")
 | 
						|
	decList := strings.Split(decimals, "\n")
 | 
						|
	addrList := strings.Split(addresses, "\n")
 | 
						|
 | 
						|
	logg.Tracef("found", "symlist", symList, "syms", symbols, "input", input)
 | 
						|
	for i, sym := range symList {
 | 
						|
		parts := strings.SplitN(sym, ":", 2)
 | 
						|
 | 
						|
		if input == parts[0] || strings.EqualFold(input, parts[1]) {
 | 
						|
			symbol = parts[1]
 | 
						|
			if i < len(balList) {
 | 
						|
				balance = strings.SplitN(balList[i], ":", 2)[1]
 | 
						|
			}
 | 
						|
			if i < len(decList) {
 | 
						|
				decimal = strings.SplitN(decList[i], ":", 2)[1]
 | 
						|
			}
 | 
						|
			if i < len(addrList) {
 | 
						|
				address = strings.SplitN(addrList[i], ":", 2)[1]
 | 
						|
			}
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
// StoreTemporaryVoucher saves voucher metadata as temporary entries in the DataStore.
 | 
						|
func StoreTemporaryVoucher(ctx context.Context, store DataStore, sessionId string, data *dataserviceapi.TokenHoldings) error {
 | 
						|
	tempData := fmt.Sprintf("%s,%s,%s,%s", data.TokenSymbol, data.Balance, data.TokenDecimals, data.TokenAddress)
 | 
						|
 | 
						|
	if err := store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(tempData)); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// GetTemporaryVoucherData retrieves temporary voucher metadata from the DataStore.
 | 
						|
func GetTemporaryVoucherData(ctx context.Context, store DataStore, sessionId string) (*dataserviceapi.TokenHoldings, error) {
 | 
						|
	temp_data, err := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	values := strings.SplitN(string(temp_data), ",", 4)
 | 
						|
 | 
						|
	data := &dataserviceapi.TokenHoldings{}
 | 
						|
 | 
						|
	data.TokenSymbol = values[0]
 | 
						|
	data.Balance = values[1]
 | 
						|
	data.TokenDecimals = values[2]
 | 
						|
	data.TokenAddress = values[3]
 | 
						|
 | 
						|
	return data, nil
 | 
						|
}
 | 
						|
 | 
						|
// UpdateVoucherData updates the active voucher data in the DataStore.
 | 
						|
func UpdateVoucherData(ctx context.Context, store DataStore, sessionId string, data *dataserviceapi.TokenHoldings) error {
 | 
						|
	logg.TraceCtxf(ctx, "dtal", "data", data)
 | 
						|
	// Active voucher data entries
 | 
						|
	activeEntries := map[storedb.DataTyp][]byte{
 | 
						|
		storedb.DATA_ACTIVE_SYM:     []byte(data.TokenSymbol),
 | 
						|
		storedb.DATA_ACTIVE_BAL:     []byte(data.Balance),
 | 
						|
		storedb.DATA_ACTIVE_DECIMAL: []byte(data.TokenDecimals),
 | 
						|
		storedb.DATA_ACTIVE_ADDRESS: []byte(data.TokenAddress),
 | 
						|
	}
 | 
						|
 | 
						|
	// Write active data
 | 
						|
	for key, value := range activeEntries {
 | 
						|
		if err := store.WriteEntry(ctx, sessionId, key, value); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// FormatVoucherList combines the voucher symbols with their balances (SRF 0.11)
 | 
						|
func FormatVoucherList(ctx context.Context, symbolsData, balancesData string) []string {
 | 
						|
	symbols := strings.Split(symbolsData, "\n")
 | 
						|
	balances := strings.Split(balancesData, "\n")
 | 
						|
 | 
						|
	var combined []string
 | 
						|
	for i := 0; i < len(symbols) && i < len(balances); i++ {
 | 
						|
		symbolParts := strings.SplitN(symbols[i], ":", 2)
 | 
						|
		balanceParts := strings.SplitN(balances[i], ":", 2)
 | 
						|
 | 
						|
		if len(symbolParts) == 2 && len(balanceParts) == 2 {
 | 
						|
			index := strings.TrimSpace(symbolParts[0])
 | 
						|
			symbol := strings.TrimSpace(symbolParts[1])
 | 
						|
			rawBalance := strings.TrimSpace(balanceParts[1])
 | 
						|
 | 
						|
			formattedBalance, err := TruncateDecimalString(rawBalance, 2)
 | 
						|
			if err != nil {
 | 
						|
				logg.ErrorCtxf(ctx, "failed to format balance", "balance", rawBalance, "error", err)
 | 
						|
				formattedBalance = rawBalance
 | 
						|
			}
 | 
						|
 | 
						|
			combined = append(combined, fmt.Sprintf("%s: %s %s", index, symbol, formattedBalance))
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return combined
 | 
						|
}
 |