project-tidyup - menuhandler and tests #90
| @ -2309,416 +2309,3 @@ func (h *MenuHandlers) ClearTemporaryValue(ctx context.Context, sym string, inpu | ||||
| 	} | ||||
| 	return res, nil | ||||
| } | ||||
| 
 | ||||
| // GetPools fetches a list of 5 top pools
 | ||||
| func (h *MenuHandlers) GetPools(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 | ||||
| 
 | ||||
| 	flag_api_error, _ := h.flagManager.GetFlag("flag_api_error") | ||||
| 
 | ||||
| 	// call the api to get a list of top 5 pools sorted by swaps
 | ||||
| 	topPools, err := h.accountService.FetchTopPools(ctx) | ||||
| 	if err != nil { | ||||
| 		res.FlagSet = append(res.FlagSet, flag_api_error) | ||||
| 		logg.ErrorCtxf(ctx, "failed on FetchTransactions", "error", err) | ||||
| 		return res, err | ||||
| 	} | ||||
| 
 | ||||
| 	// Return if there are no pools
 | ||||
| 	if len(topPools) == 0 { | ||||
| 		return res, nil | ||||
| 	} | ||||
| 
 | ||||
| 	data := store.ProcessPools(topPools) | ||||
| 
 | ||||
| 	// Store all Pool data
 | ||||
| 	dataMap := map[storedb.DataTyp]string{ | ||||
| 		storedb.DATA_POOL_NAMES:     data.PoolNames, | ||||
| 		storedb.DATA_POOL_SYMBOLS:   data.PoolSymbols, | ||||
| 		storedb.DATA_POOL_ADDRESSES: data.PoolContractAdrresses, | ||||
| 	} | ||||
| 
 | ||||
| 	// Write data entries
 | ||||
| 	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.PoolSymbols) | ||||
| 
 | ||||
| 	return res, nil | ||||
| } | ||||
| 
 | ||||
| // 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 | ||||
| } | ||||
|  | ||||
							
								
								
									
										427
									
								
								handlers/application/poolswap.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										427
									
								
								handlers/application/poolswap.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,427 @@ | ||||
| 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" | ||||
| ) | ||||
| 
 | ||||
| // GetPools fetches a list of 5 top pools
 | ||||
| func (h *MenuHandlers) GetPools(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 | ||||
| 
 | ||||
| 	flag_api_error, _ := h.flagManager.GetFlag("flag_api_error") | ||||
| 
 | ||||
| 	// call the api to get a list of top 5 pools sorted by swaps
 | ||||
| 	topPools, err := h.accountService.FetchTopPools(ctx) | ||||
| 	if err != nil { | ||||
| 		res.FlagSet = append(res.FlagSet, flag_api_error) | ||||
| 		logg.ErrorCtxf(ctx, "failed on FetchTransactions", "error", err) | ||||
| 		return res, err | ||||
| 	} | ||||
| 
 | ||||
| 	// Return if there are no pools
 | ||||
| 	if len(topPools) == 0 { | ||||
| 		return res, nil | ||||
| 	} | ||||
| 
 | ||||
| 	data := store.ProcessPools(topPools) | ||||
| 
 | ||||
| 	// Store all Pool data
 | ||||
| 	dataMap := map[storedb.DataTyp]string{ | ||||
| 		storedb.DATA_POOL_NAMES:     data.PoolNames, | ||||
| 		storedb.DATA_POOL_SYMBOLS:   data.PoolSymbols, | ||||
| 		storedb.DATA_POOL_ADDRESSES: data.PoolContractAdrresses, | ||||
| 	} | ||||
| 
 | ||||
| 	// Write data entries
 | ||||
| 	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.PoolSymbols) | ||||
| 
 | ||||
| 	return res, nil | ||||
| } | ||||
| 
 | ||||
| // 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 | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user