246 lines
7.5 KiB
Go
246 lines
7.5 KiB
Go
package application
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"strconv"
|
||
|
||
"git.defalsify.org/vise.git/db"
|
||
"git.defalsify.org/vise.git/resource"
|
||
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
|
||
"gopkg.in/leonelquinteros/gotext.v1"
|
||
)
|
||
|
||
// CheckBalance retrieves the balance of the active voucher and sets
|
||
// the balance as the result content.
|
||
func (h *MenuHandlers) CheckBalance(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||
var (
|
||
res resource.Result
|
||
err error
|
||
content string
|
||
)
|
||
|
||
sessionId, ok := ctx.Value("SessionId").(string)
|
||
if !ok {
|
||
return res, fmt.Errorf("missing session")
|
||
}
|
||
|
||
store := h.userdataStore
|
||
|
||
// get the active sym and active balance
|
||
activeSym, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM)
|
||
if err != nil {
|
||
logg.InfoCtxf(ctx, "could not find the activeSym in checkBalance:", "err", err)
|
||
if !db.IsNotFound(err) {
|
||
logg.ErrorCtxf(ctx, "failed to read activeSym entry with", "key", storedb.DATA_ACTIVE_SYM, "error", err)
|
||
return res, err
|
||
}
|
||
}
|
||
|
||
activeBal, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_BAL)
|
||
if err != nil {
|
||
if !db.IsNotFound(err) {
|
||
logg.ErrorCtxf(ctx, "failed to read activeBal entry with", "key", storedb.DATA_ACTIVE_BAL, "error", err)
|
||
return res, err
|
||
}
|
||
}
|
||
|
||
accAlias, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACCOUNT_ALIAS)
|
||
if err != nil {
|
||
if !db.IsNotFound(err) {
|
||
logg.ErrorCtxf(ctx, "failed to read account alias entry with", "key", storedb.DATA_ACCOUNT_ALIAS, "error", err)
|
||
return res, err
|
||
}
|
||
}
|
||
|
||
content, err = loadUserContent(ctx, string(activeSym), string(activeBal), string(accAlias))
|
||
if err != nil {
|
||
return res, err
|
||
}
|
||
res.Content = content
|
||
|
||
return res, nil
|
||
}
|
||
|
||
// loadUserContent loads the main user content in the main menu: the alias, balance and active symbol associated with active voucher
|
||
func loadUserContent(ctx context.Context, activeSym, balance, alias string) (string, error) {
|
||
var content string
|
||
|
||
code := codeFromCtx(ctx)
|
||
l := gotext.NewLocale(translationDir, code)
|
||
l.AddDomain("default")
|
||
|
||
// Format the balance to 2 decimal places or default to 0.00
|
||
formattedAmount, err := store.TruncateDecimalString(balance, 2)
|
||
if err != nil {
|
||
formattedAmount = "0.00"
|
||
}
|
||
|
||
// format the final outputs
|
||
balStr := fmt.Sprintf("%s %s", formattedAmount, activeSym)
|
||
|
||
if alias != "" {
|
||
content = l.Get("%s\nBalance: %s\n", alias, balStr)
|
||
} else {
|
||
content = l.Get("Balance: %s\n", balStr)
|
||
}
|
||
return content, nil
|
||
}
|
||
|
||
// FetchCommunityBalance retrieves and displays the balance for community accounts in user's preferred language.
|
||
func (h *MenuHandlers) FetchCommunityBalance(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||
var res resource.Result
|
||
// retrieve the language code from the context
|
||
code := codeFromCtx(ctx)
|
||
// Initialize the localization system with the appropriate translation directory
|
||
l := gotext.NewLocale(translationDir, code)
|
||
l.AddDomain("default")
|
||
//TODO:
|
||
//Check if the address is a community account,if then,get the actual balance
|
||
res.Content = l.Get("Community Balance: 0.00")
|
||
return res, nil
|
||
}
|
||
|
||
// CalculateCreditAndDebt calls the API to get the credit and debt
|
||
// uses the pretium rates to convert the value to Ksh
|
||
func (h *MenuHandlers) CalculateCreditAndDebt(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||
var res resource.Result
|
||
sessionId, ok := ctx.Value("SessionId").(string)
|
||
if !ok {
|
||
return res, fmt.Errorf("missing session")
|
||
}
|
||
|
||
code := codeFromCtx(ctx)
|
||
l := gotext.NewLocale(translationDir, code)
|
||
l.AddDomain("default")
|
||
|
||
flag_api_call_error, _ := h.flagManager.GetFlag("flag_api_call_error")
|
||
|
||
// Fetch session data
|
||
_, activeBal, activeSym, activeAddress, publicKey, activeDecimal, err := h.getSessionData(ctx, sessionId)
|
||
if err != nil {
|
||
res.Content = l.Get("Credit: %s KSH\nDebt: %s %s\n", "0", "0", string(activeSym))
|
||
return res, nil
|
||
}
|
||
|
||
res.FlagReset = append(res.FlagReset, flag_api_call_error)
|
||
|
||
// Resolve active pool
|
||
activePoolAddress, _, err := h.resolveActivePoolDetails(ctx, sessionId)
|
||
if err != nil {
|
||
return res, err
|
||
}
|
||
|
||
// Fetch swappable vouchers (pool view)
|
||
swappableVouchers, err := h.accountService.GetPoolSwappableFromVouchers(ctx, string(activePoolAddress), string(publicKey))
|
||
if err != nil {
|
||
logg.ErrorCtxf(ctx, "failed on GetPoolSwappableFromVouchers", "error", err)
|
||
res.Content = l.Get("Credit: %s KSH\nDebt: %s %s\n", "0", "0", string(activeSym))
|
||
return res, nil
|
||
}
|
||
|
||
if len(swappableVouchers) == 0 {
|
||
res.Content = l.Get("Credit: %s KSH\nDebt: %s %s\n", "0", "0", string(activeSym))
|
||
return res, nil
|
||
}
|
||
|
||
// Fetch ALL wallet vouchers (voucher holdings view)
|
||
allVouchers, err := h.accountService.FetchVouchers(ctx, string(publicKey))
|
||
if err != nil {
|
||
logg.ErrorCtxf(ctx, "failed on FetchVouchers", "error", err)
|
||
return res, nil
|
||
}
|
||
|
||
// CREDIT calculation
|
||
// Rule:
|
||
// 1. Swap quote of active voucher → first stable in pool from GetPoolSwappableFromVouchers
|
||
// 2. PLUS all stable balances from FetchVouchers
|
||
|
||
scaledCredit := "0"
|
||
|
||
// 1️. Find first stable voucher in POOL (for swap target)
|
||
var firstPoolStable *dataserviceapi.TokenHoldings
|
||
for i := range swappableVouchers {
|
||
if isStableVoucher(swappableVouchers[i].TokenAddress) {
|
||
firstPoolStable = &swappableVouchers[i]
|
||
break
|
||
}
|
||
}
|
||
|
||
// 2️. If pool has a stable, get swap quote
|
||
if firstPoolStable != nil {
|
||
finalAmountStr, err := store.ParseAndScaleAmount(
|
||
string(activeBal),
|
||
string(activeDecimal),
|
||
)
|
||
if err != nil {
|
||
return res, err
|
||
}
|
||
|
||
// swap active -> FIRST stable from pool list
|
||
r, err := h.accountService.GetPoolSwapQuote(ctx, finalAmountStr, string(publicKey), string(activeAddress), string(activePoolAddress), firstPoolStable.TokenAddress)
|
||
if err != nil {
|
||
res.FlagSet = append(res.FlagSet, flag_api_call_error)
|
||
res.Content = l.Get("Your request failed. Please try again later.")
|
||
logg.ErrorCtxf(ctx, "failed on poolSwap", "error", err)
|
||
return res, nil
|
||
}
|
||
|
||
// scale using REAL stable decimals
|
||
finalQuote := store.ScaleDownBalance(r.OutValue, firstPoolStable.TokenDecimals)
|
||
|
||
scaledCredit = store.AddDecimalStrings(scaledCredit, finalQuote)
|
||
}
|
||
|
||
// 3️. Add ALL wallet stable balances (from FetchVouchers)
|
||
for _, v := range allVouchers {
|
||
if isStableVoucher(v.TokenAddress) {
|
||
scaled := store.ScaleDownBalance(v.Balance, v.TokenDecimals)
|
||
scaledCredit = store.AddDecimalStrings(scaledCredit, scaled)
|
||
}
|
||
}
|
||
|
||
// DEBT calculation
|
||
// Rule:
|
||
// - Default = 0
|
||
// - If active is stable → remain 0
|
||
// - If active is non-stable and exists in pool → use pool balance
|
||
|
||
scaledDebt := "0"
|
||
|
||
if !isStableVoucher(string(activeAddress)) {
|
||
for _, v := range swappableVouchers {
|
||
if v.TokenSymbol == string(activeSym) {
|
||
scaledDebt = store.ScaleDownBalance(v.Balance, v.TokenDecimals)
|
||
break
|
||
}
|
||
}
|
||
}
|
||
|
||
formattedDebt, _ := store.TruncateDecimalString(scaledDebt, 2)
|
||
|
||
// Fetch MPESA rates
|
||
rates, err := h.accountService.GetMpesaOnrampRates(ctx)
|
||
if err != nil {
|
||
res.FlagSet = append(res.FlagSet, flag_api_call_error)
|
||
res.Content = l.Get("Your request failed. Please try again later.")
|
||
logg.ErrorCtxf(ctx, "failed on GetMpesaOnrampRates", "error", err)
|
||
return res, nil
|
||
}
|
||
|
||
creditFloat, _ := strconv.ParseFloat(scaledCredit, 64)
|
||
creditKsh := fmt.Sprintf("%f", creditFloat*rates.Buy)
|
||
kshFormattedCredit, _ := store.TruncateDecimalString(creditKsh, 0)
|
||
|
||
res.Content = l.Get(
|
||
"Credit: %s KSH\nDebt: %s %s\n",
|
||
kshFormattedCredit,
|
||
formattedDebt,
|
||
string(activeSym),
|
||
)
|
||
|
||
return res, nil
|
||
}
|