package common import ( "context" "fmt" "math/big" "strings" "git.grassecon.net/urdt/ussd/internal/storage" dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" ) // VoucherMetadata helps organize data fields type VoucherMetadata struct { Symbols string Balances string Decimals string Addresses string } // 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 { symbols = append(symbols, fmt.Sprintf("%d:%s", i+1, h.TokenSymbol)) // 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.ContractAddress)) } 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, db storage.PrefixDb, input string) (*dataserviceapi.TokenHoldings, error) { keys := []DataTyp{DATA_VOUCHER_SYMBOLS, DATA_VOUCHER_BALANCES, DATA_VOUCHER_DECIMALS, DATA_VOUCHER_ADDRESSES} data := make(map[DataTyp]string) for _, key := range keys { value, err := db.Get(ctx, ToBytes(key)) if err != nil { return nil, fmt.Errorf("failed to get %s: %v", ToBytes(key), err) } data[key] = string(value) } symbol, balance, decimal, address := MatchVoucher(input, data[DATA_VOUCHER_SYMBOLS], data[DATA_VOUCHER_BALANCES], data[DATA_VOUCHER_DECIMALS], data[DATA_VOUCHER_ADDRESSES], ) if symbol == "" { return nil, nil } return &dataserviceapi.TokenHoldings{ TokenSymbol: string(symbol), Balance: string(balance), TokenDecimals: string(decimal), ContractAddress: 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.ContractAddress) if err := store.WriteEntry(ctx, sessionId, 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, 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.ContractAddress = 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[DataTyp][]byte{ DATA_ACTIVE_SYM: []byte(data.TokenSymbol), DATA_ACTIVE_BAL: []byte(data.Balance), DATA_ACTIVE_DECIMAL: []byte(data.TokenDecimals), DATA_ACTIVE_ADDRESS: []byte(data.ContractAddress), } // Write active data for key, value := range activeEntries { if err := store.WriteEntry(ctx, sessionId, key, value); err != nil { return err } } return nil }