382 lines
13 KiB
Go
382 lines
13 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/config"
|
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
|
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
|
"gopkg.in/leonelquinteros/gotext.v1"
|
|
)
|
|
|
|
// LoadSwapFromList returns a list of possible vouchers to swap to
|
|
func (h *MenuHandlers) LoadSwapToList(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")
|
|
}
|
|
|
|
userStore := h.userdataStore
|
|
|
|
// get the active address and symbol
|
|
activeAddress, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS)
|
|
if err != nil {
|
|
logg.ErrorCtxf(ctx, "failed to read activeAddress entry with", "key", storedb.DATA_ACTIVE_ADDRESS, "error", err)
|
|
return res, err
|
|
}
|
|
activeSym, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM)
|
|
if err != nil {
|
|
logg.ErrorCtxf(ctx, "failed to read activeSym entry with", "key", storedb.DATA_ACTIVE_SYM, "error", err)
|
|
return res, err
|
|
}
|
|
|
|
code := codeFromCtx(ctx)
|
|
l := gotext.NewLocale(translationDir, code)
|
|
l.AddDomain("default")
|
|
|
|
flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher")
|
|
flag_api_error, _ := h.flagManager.GetFlag("flag_api_error")
|
|
|
|
inputStr := string(input)
|
|
if inputStr == "0" {
|
|
return res, nil
|
|
}
|
|
|
|
// Get active pool address and symbol or fall back to default
|
|
var activePoolAddress []byte
|
|
activePoolAddress, err = userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_ADDRESS)
|
|
if err != nil {
|
|
if db.IsNotFound(err) {
|
|
defaultPoolAddress := config.DefaultPoolAddress()
|
|
// store the default as the active pool address
|
|
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_ADDRESS, []byte(defaultPoolAddress))
|
|
if err != nil {
|
|
logg.ErrorCtxf(ctx, "failed to write default PoolContractAdrress", "key", storedb.DATA_ACTIVE_POOL_ADDRESS, "value", defaultPoolAddress, "error", err)
|
|
return res, err
|
|
}
|
|
activePoolAddress = []byte(defaultPoolAddress)
|
|
} else {
|
|
logg.ErrorCtxf(ctx, "failed to read active PoolContractAdrress", "key", storedb.DATA_ACTIVE_POOL_ADDRESS, "error", err)
|
|
return res, err
|
|
}
|
|
}
|
|
|
|
var activePoolSymbol []byte
|
|
activePoolSymbol, err = userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_SYM)
|
|
if err != nil {
|
|
if db.IsNotFound(err) {
|
|
defaultPoolSym := config.DefaultPoolName()
|
|
// store the default as the active pool symbol
|
|
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_SYM, []byte(defaultPoolSym))
|
|
if err != nil {
|
|
logg.ErrorCtxf(ctx, "failed to write default Pool Symbol", "key", storedb.DATA_ACTIVE_POOL_SYM, "value", defaultPoolSym, "error", err)
|
|
return res, err
|
|
}
|
|
activePoolSymbol = []byte(defaultPoolSym)
|
|
} else {
|
|
logg.ErrorCtxf(ctx, "failed to read active Pool symbol", "key", storedb.DATA_ACTIVE_POOL_SYM, "error", err)
|
|
return res, err
|
|
}
|
|
}
|
|
|
|
// call the api using the ActivePoolAddress and ActiveVoucherAddress to check if it is part of the pool
|
|
r, err := h.accountService.CheckTokenInPool(ctx, string(activePoolAddress), string(activeAddress))
|
|
if err != nil {
|
|
res.FlagSet = append(res.FlagSet, flag_api_error)
|
|
logg.ErrorCtxf(ctx, "failed on CheckTokenInPool", "error", err)
|
|
return res, err
|
|
}
|
|
|
|
logg.InfoCtxf(ctx, "CheckTokenInPool", "response", r, "active_pool_address", string(activePoolAddress), "active_symbol_address", string(activeAddress))
|
|
|
|
if !r.CanSwapFrom {
|
|
res.FlagSet = append(res.FlagSet, flag_incorrect_voucher)
|
|
res.Content = l.Get(
|
|
"%s is not in %s. Please update your voucher and try again.",
|
|
activeSym,
|
|
activePoolSymbol,
|
|
)
|
|
return res, nil
|
|
}
|
|
|
|
res.FlagReset = append(res.FlagReset, flag_incorrect_voucher)
|
|
|
|
// call the api using the activePoolAddress to get a list of SwapToSymbolsData
|
|
swapToList, err := h.accountService.GetPoolSwappableVouchers(ctx, string(activePoolAddress))
|
|
if err != nil {
|
|
res.FlagSet = append(res.FlagSet, flag_api_error)
|
|
logg.ErrorCtxf(ctx, "failed on FetchTransactions", "error", err)
|
|
return res, err
|
|
}
|
|
|
|
logg.InfoCtxf(ctx, "GetPoolSwappableVouchers", "swapToList", swapToList)
|
|
|
|
// Return if there are no vouchers
|
|
if len(swapToList) == 0 {
|
|
return res, nil
|
|
}
|
|
|
|
data := store.ProcessVouchers(swapToList)
|
|
|
|
logg.InfoCtxf(ctx, "ProcessVouchers", "data", data)
|
|
|
|
// Store all swap_to tokens data
|
|
dataMap := map[storedb.DataTyp]string{
|
|
storedb.DATA_POOL_TO_SYMBOLS: data.Symbols,
|
|
storedb.DATA_POOL_TO_BALANCES: data.Balances,
|
|
storedb.DATA_POOL_TO_DECIMALS: data.Decimals,
|
|
storedb.DATA_POOL_TO_ADDRESSES: data.Addresses,
|
|
}
|
|
|
|
for key, value := range dataMap {
|
|
if err := userStore.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil {
|
|
logg.ErrorCtxf(ctx, "Failed to write data entry for sessionId: %s", sessionId, "key", key, "error", err)
|
|
continue
|
|
}
|
|
}
|
|
|
|
res.Content = h.ReplaceSeparatorFunc(data.Symbols)
|
|
|
|
return res, nil
|
|
}
|
|
|
|
// SwapMaxLimit returns the max FROM token
|
|
// check if max/tokenDecimals > 0.1 for UX purposes and to prevent swapping of dust values
|
|
func (h *MenuHandlers) SwapMaxLimit(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")
|
|
}
|
|
|
|
flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher")
|
|
flag_api_error, _ := h.flagManager.GetFlag("flag_api_error")
|
|
flag_low_swap_amount, _ := h.flagManager.GetFlag("flag_low_swap_amount")
|
|
|
|
res.FlagReset = append(res.FlagReset, flag_incorrect_voucher, flag_low_swap_amount)
|
|
|
|
inputStr := string(input)
|
|
if inputStr == "0" {
|
|
return res, nil
|
|
}
|
|
|
|
userStore := h.userdataStore
|
|
metadata, err := store.GetSwapToVoucherData(ctx, userStore, sessionId, inputStr)
|
|
if err != nil {
|
|
return res, fmt.Errorf("failed to retrieve swap to voucher data: %v", err)
|
|
}
|
|
if metadata == nil {
|
|
res.FlagSet = append(res.FlagSet, flag_incorrect_voucher)
|
|
return res, nil
|
|
}
|
|
|
|
logg.InfoCtxf(ctx, "Metadata from GetSwapToVoucherData:", "metadata", metadata)
|
|
|
|
// Store the active swap_to data
|
|
if err := store.UpdateSwapToVoucherData(ctx, userStore, sessionId, metadata); err != nil {
|
|
logg.ErrorCtxf(ctx, "failed on UpdateSwapToVoucherData", "error", err)
|
|
return res, err
|
|
}
|
|
|
|
swapData, err := store.ReadSwapData(ctx, userStore, sessionId)
|
|
if err != nil {
|
|
return res, err
|
|
}
|
|
|
|
// call the api using the ActivePoolAddress, ActiveSwapFromAddress, ActiveSwapToAddress and PublicKey to get the swap max limit
|
|
logg.InfoCtxf(ctx, "Call GetSwapFromTokenMaxLimit with:", "ActivePoolAddress", swapData.ActivePoolAddress, "ActiveSwapFromAddress", swapData.ActiveSwapFromAddress, "ActiveSwapToAddress", swapData.ActiveSwapToAddress, "publicKey", swapData.PublicKey)
|
|
r, err := h.accountService.GetSwapFromTokenMaxLimit(ctx, swapData.ActivePoolAddress, swapData.ActiveSwapFromAddress, swapData.ActiveSwapToAddress, swapData.PublicKey)
|
|
if err != nil {
|
|
res.FlagSet = append(res.FlagSet, flag_api_error)
|
|
logg.ErrorCtxf(ctx, "failed on GetSwapFromTokenMaxLimit", "error", err)
|
|
return res, err
|
|
}
|
|
|
|
// Scale down the amount
|
|
maxAmountStr := store.ScaleDownBalance(r.Max, swapData.ActiveSwapFromDecimal)
|
|
if err != nil {
|
|
return res, err
|
|
}
|
|
|
|
maxAmountFloat, err := strconv.ParseFloat(maxAmountStr, 64)
|
|
if err != nil {
|
|
logg.ErrorCtxf(ctx, "failed to parse maxAmountStr as float", "value", maxAmountStr, "error", err)
|
|
return res, err
|
|
}
|
|
|
|
// Format to 2 decimal places
|
|
maxStr := fmt.Sprintf("%.2f", maxAmountFloat)
|
|
|
|
if maxAmountFloat < 0.1 {
|
|
// return with low amount flag
|
|
res.Content = maxStr
|
|
res.FlagSet = append(res.FlagSet, flag_low_swap_amount)
|
|
return res, nil
|
|
}
|
|
|
|
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SWAP_MAX_AMOUNT, []byte(maxStr))
|
|
if err != nil {
|
|
logg.ErrorCtxf(ctx, "failed to write swap max amount entry with", "key", storedb.DATA_ACTIVE_SWAP_MAX_AMOUNT, "value", maxStr, "error", err)
|
|
return res, err
|
|
}
|
|
|
|
res.Content = fmt.Sprintf(
|
|
"Maximum: %s\n\nEnter amount of %s to swap for %s:",
|
|
maxStr, swapData.ActiveSwapFromSym, swapData.ActiveSwapToSym,
|
|
)
|
|
|
|
return res, nil
|
|
}
|
|
|
|
// SwapPreview displays the swap preview and estimates
|
|
func (h *MenuHandlers) SwapPreview(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")
|
|
}
|
|
|
|
inputStr := string(input)
|
|
if inputStr == "0" {
|
|
return res, nil
|
|
}
|
|
|
|
flag_invalid_amount, _ := h.flagManager.GetFlag("flag_invalid_amount")
|
|
|
|
code := codeFromCtx(ctx)
|
|
l := gotext.NewLocale(translationDir, code)
|
|
l.AddDomain("default")
|
|
|
|
userStore := h.userdataStore
|
|
|
|
swapData, err := store.ReadSwapPreviewData(ctx, userStore, sessionId)
|
|
if err != nil {
|
|
return res, err
|
|
}
|
|
|
|
maxValue, err := strconv.ParseFloat(swapData.ActiveSwapMaxAmount, 64)
|
|
if err != nil {
|
|
logg.ErrorCtxf(ctx, "Failed to convert the swapMaxAmount to a float", "error", err)
|
|
return res, err
|
|
}
|
|
|
|
inputAmount, err := strconv.ParseFloat(inputStr, 64)
|
|
if err != nil || inputAmount > maxValue {
|
|
res.FlagSet = append(res.FlagSet, flag_invalid_amount)
|
|
res.Content = inputStr
|
|
return res, nil
|
|
}
|
|
|
|
// Format the amount to 2 decimal places
|
|
formattedAmount, err := store.TruncateDecimalString(inputStr, 2)
|
|
if err != nil {
|
|
res.FlagSet = append(res.FlagSet, flag_invalid_amount)
|
|
res.Content = inputStr
|
|
return res, nil
|
|
}
|
|
|
|
finalAmountStr, err := store.ParseAndScaleAmount(formattedAmount, swapData.ActiveSwapFromDecimal)
|
|
if err != nil {
|
|
return res, err
|
|
}
|
|
|
|
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SWAP_AMOUNT, []byte(finalAmountStr))
|
|
if err != nil {
|
|
logg.ErrorCtxf(ctx, "failed to write swap amount entry with", "key", storedb.DATA_ACTIVE_SWAP_AMOUNT, "value", finalAmountStr, "error", err)
|
|
return res, err
|
|
}
|
|
// store the user's input amount in the temporary value
|
|
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(inputStr))
|
|
if err != nil {
|
|
logg.ErrorCtxf(ctx, "failed to write swap amount entry with", "key", storedb.DATA_ACTIVE_SWAP_AMOUNT, "value", finalAmountStr, "error", err)
|
|
return res, err
|
|
}
|
|
|
|
// call the API to get the quote
|
|
r, err := h.accountService.GetPoolSwapQuote(ctx, finalAmountStr, swapData.PublicKey, swapData.ActiveSwapFromAddress, swapData.ActivePoolAddress, swapData.ActiveSwapToAddress)
|
|
if err != nil {
|
|
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
|
|
res.FlagSet = append(res.FlagSet, flag_api_error)
|
|
res.Content = l.Get("Your request failed. Please try again later.")
|
|
logg.ErrorCtxf(ctx, "failed on poolSwap", "error", err)
|
|
return res, nil
|
|
}
|
|
|
|
// Scale down the quoted amount
|
|
quoteAmountStr := store.ScaleDownBalance(r.OutValue, swapData.ActiveSwapToDecimal)
|
|
qouteAmount, err := strconv.ParseFloat(quoteAmountStr, 64)
|
|
if err != nil {
|
|
logg.ErrorCtxf(ctx, "failed to parse quoteAmountStr as float", "value", quoteAmountStr, "error", err)
|
|
return res, err
|
|
}
|
|
|
|
// Format to 2 decimal places
|
|
qouteStr := fmt.Sprintf("%.2f", qouteAmount)
|
|
|
|
res.Content = fmt.Sprintf(
|
|
"You will swap:\n%s %s for %s %s:",
|
|
inputStr, swapData.ActiveSwapFromSym, qouteStr, swapData.ActiveSwapToSym,
|
|
)
|
|
|
|
return res, nil
|
|
}
|
|
|
|
// InitiateSwap calls the poolSwap and returns a confirmation based on the result.
|
|
func (h *MenuHandlers) InitiateSwap(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
|
var res resource.Result
|
|
var err error
|
|
sessionId, ok := ctx.Value("SessionId").(string)
|
|
if !ok {
|
|
return res, fmt.Errorf("missing session")
|
|
}
|
|
|
|
flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized")
|
|
|
|
code := codeFromCtx(ctx)
|
|
l := gotext.NewLocale(translationDir, code)
|
|
l.AddDomain("default")
|
|
|
|
userStore := h.userdataStore
|
|
|
|
swapData, err := store.ReadSwapPreviewData(ctx, userStore, sessionId)
|
|
if err != nil {
|
|
return res, err
|
|
}
|
|
|
|
swapAmount, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SWAP_AMOUNT)
|
|
if err != nil {
|
|
logg.ErrorCtxf(ctx, "failed to read swapAmount entry with", "key", storedb.DATA_ACTIVE_SWAP_AMOUNT, "error", err)
|
|
return res, err
|
|
}
|
|
|
|
swapAmountStr := string(swapAmount)
|
|
|
|
// Call the poolSwap API
|
|
r, err := h.accountService.PoolSwap(ctx, swapAmountStr, swapData.PublicKey, swapData.ActiveSwapFromAddress, swapData.ActivePoolAddress, swapData.ActiveSwapToAddress)
|
|
if err != nil {
|
|
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
|
|
res.FlagSet = append(res.FlagSet, flag_api_error)
|
|
res.Content = l.Get("Your request failed. Please try again later.")
|
|
logg.ErrorCtxf(ctx, "failed on poolSwap", "error", err)
|
|
return res, nil
|
|
}
|
|
|
|
trackingId := r.TrackingId
|
|
logg.InfoCtxf(ctx, "poolSwap", "trackingId", trackingId)
|
|
|
|
res.Content = l.Get(
|
|
"Your request has been sent. You will receive an SMS when your %s %s has been swapped for %s.",
|
|
swapData.TemporaryValue,
|
|
swapData.ActiveSwapFromSym,
|
|
swapData.ActiveSwapToSym,
|
|
)
|
|
|
|
res.FlagReset = append(res.FlagReset, flag_account_authorized)
|
|
return res, nil
|
|
}
|