From c8146ea211ac631470a9ea25200202f0cf0c0c4a Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Mon, 19 May 2025 15:44:50 +0200 Subject: [PATCH 01/25] alfred/pool-swap (#33) Pool swap functionality Resolves issue https://git.grassecon.net/grassrootseconomics/sarafu-vise/issues/24 Reviewed-on: https://git.grassecon.net/grassrootseconomics/sarafu-vise/pulls/33 Co-authored-by: alfred-mk Co-committed-by: alfred-mk --- go.mod | 4 +- go.sum | 10 + handlers/application/menuhandler.go | 375 ++++++++++++++++++++ handlers/local.go | 5 +- services/registration/api_failure | 2 +- services/registration/locale/swa/default.po | 7 +- services/registration/low_swap_amount | 1 + services/registration/low_swap_amount.vis | 6 + services/registration/low_swap_amount_swa | 1 + services/registration/main.vis | 14 +- services/registration/missing_voucher | 1 + services/registration/missing_voucher.vis | 6 + services/registration/missing_voucher_swa | 1 + services/registration/pp.csv | 5 +- services/registration/swap_initiated.vis | 3 + services/registration/swap_limit | 1 + services/registration/swap_limit.vis | 6 + services/registration/swap_limit_swa | 1 + services/registration/swap_menu | 1 + services/registration/swap_preview | 3 + services/registration/swap_preview.vis | 12 + services/registration/swap_preview_swa | 3 + services/registration/swap_to_list | 2 + services/registration/swap_to_list.vis | 12 + services/registration/swap_to_list_swa | 2 + store/db/db.go | 43 +++ store/pools.go | 93 +++++ store/swap.go | 187 ++++++++++ store/swap_test.go | 146 ++++++++ store/tokens.go | 4 +- 30 files changed, 943 insertions(+), 14 deletions(-) create mode 100644 services/registration/low_swap_amount create mode 100644 services/registration/low_swap_amount.vis create mode 100644 services/registration/low_swap_amount_swa create mode 100644 services/registration/missing_voucher create mode 100644 services/registration/missing_voucher.vis create mode 100644 services/registration/missing_voucher_swa create mode 100644 services/registration/swap_initiated.vis create mode 100644 services/registration/swap_limit create mode 100644 services/registration/swap_limit.vis create mode 100644 services/registration/swap_limit_swa create mode 100644 services/registration/swap_menu create mode 100644 services/registration/swap_preview create mode 100644 services/registration/swap_preview.vis create mode 100644 services/registration/swap_preview_swa create mode 100644 services/registration/swap_to_list create mode 100644 services/registration/swap_to_list.vis create mode 100644 services/registration/swap_to_list_swa create mode 100644 store/pools.go create mode 100644 store/swap.go create mode 100644 store/swap_test.go diff --git a/go.mod b/go.mod index d7a837f..0a488e4 100644 --- a/go.mod +++ b/go.mod @@ -5,12 +5,12 @@ go 1.23.4 require ( git.defalsify.org/vise.git v0.3.2-0.20250507172020-cb22240f1cb9 git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20250417111317-2953f4c2f32e - git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250428082711-5d221b8d565f + git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250517114512-050998ff82b1 git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306 git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694 github.com/alecthomas/assert/v2 v2.2.2 github.com/gofrs/uuid v4.4.0+incompatible - github.com/grassrootseconomics/ussd-data-service v1.2.0-beta + github.com/grassrootseconomics/ussd-data-service v1.4.4-beta github.com/jackc/pgx/v5 v5.7.1 github.com/peteole/testdata-loader v0.3.0 github.com/stretchr/testify v1.9.0 diff --git a/go.sum b/go.sum index 75de564..14da9e9 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,12 @@ git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20250417111317-2953 git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20250417111317-2953f4c2f32e/go.mod h1:wgQJZGIS6QuNLHqDhcsvehsbn5PvgV7aziRebMnJi60= git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250428082711-5d221b8d565f h1:OAHCP3YR1C5h1WFnnEnLs5kn6jTxQHQYWYtQaMZJIMY= git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250428082711-5d221b8d565f/go.mod h1:gOn89ipaDcDvmQXRMQYKUqcw/sJcwVOPVt2eC6Geip8= +git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250516094326-3b85167ad84a h1:QNh0NaKtGbSeRPlTVKEAnqc0R5rnVrpDMrCHD/EaU5U= +git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250516094326-3b85167ad84a/go.mod h1:K/TPgZ4OhPHBQq2X0ab3JZs4YjiexzSURZcfHLs9Pf4= +git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250517113706-ee434dba6980 h1:FQUwTDFWduY7gCMi1U2OiFlsuAHLojWUw2hvZ4cGC2s= +git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250517113706-ee434dba6980/go.mod h1:K/TPgZ4OhPHBQq2X0ab3JZs4YjiexzSURZcfHLs9Pf4= +git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250517114512-050998ff82b1 h1:0hvILlGkZnXO8o7nZth4xu77vAS4zVQ6Ae0rb5x/Idg= +git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250517114512-050998ff82b1/go.mod h1:K/TPgZ4OhPHBQq2X0ab3JZs4YjiexzSURZcfHLs9Pf4= git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306 h1:Jo+yWysWw/N5BJQtAyEMN8ePVvAyPHv+JG4lQti+1N4= git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306/go.mod h1:FdLwYtzsjOIcDiW4uDgDYnB4Wdzq12uJUe0QHSSPbSo= git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694 h1:DjJlBSz0S13acft5XZDWk7ZYnzElym0xLMYEVgyNJ+E= @@ -64,6 +70,10 @@ github.com/grassrootseconomics/ethutils v1.3.1 h1:LlQO90HjJkl7ejC8fv6jP7RJUrAm1j github.com/grassrootseconomics/ethutils v1.3.1/go.mod h1:Wuv1VEZrkLIXqTSEYI3Nh9HG/ZHOUQ+U+xvWJ8QtjgQ= github.com/grassrootseconomics/ussd-data-service v1.2.0-beta h1:fn1gwbWIwHVEBtUC2zi5OqTlfI/5gU1SMk0fgGixIXk= github.com/grassrootseconomics/ussd-data-service v1.2.0-beta/go.mod h1:omfI0QtUwIdpu9gMcUqLMCG8O1XWjqJGBx1qUMiGWC0= +github.com/grassrootseconomics/ussd-data-service v1.4.0-beta h1:4fMd/3h2ZIhRg4GdHQmRw5FfD3MpJvFNNJQo+Q27f5M= +github.com/grassrootseconomics/ussd-data-service v1.4.0-beta/go.mod h1:9sGnorpKaK76FmOGXoh/xv7x5siSFNYdXxQo9BKW4DI= +github.com/grassrootseconomics/ussd-data-service v1.4.4-beta h1:turlyo0i3OLj29mWpWNoB/3Qao8qEngT/5d1jDWTZE4= +github.com/grassrootseconomics/ussd-data-service v1.4.4-beta/go.mod h1:9sGnorpKaK76FmOGXoh/xv7x5siSFNYdXxQo9BKW4DI= github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 h1:U4kkNYryi/qfbBF8gh7Vsbuz+cVmhf5kt6pE9bYYyLo= github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4/go.mod h1:zpZDgZFzeq9s0MIeB1P50NIEWDFFHSFBohI/NbaTD/Y= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= diff --git a/handlers/application/menuhandler.go b/handlers/application/menuhandler.go index ba31717..40abbe5 100644 --- a/handlers/application/menuhandler.go +++ b/handlers/application/menuhandler.go @@ -2651,3 +2651,378 @@ 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 + publicKey, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY) + if err != nil { + logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err) + return res, err + } + + // 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 + } + + defaultPool := dataserviceapi.PoolDetails{ + PoolName: "Kenya ROLA Pool", + PoolSymbol: "ROLA", + PoolContractAdrress: "0x48a953cA5cf5298bc6f6Af3C608351f537AAcb9e", + LimiterAddress: "", + VoucherRegistry: "", + } + + activePoolAddress := defaultPool.PoolContractAdrress + + // set the active pool contract address + err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_ADDRESS, []byte(activePoolAddress)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write active PoolContractAdrress entry with", "key", storedb.DATA_ACTIVE_POOL_ADDRESS, "value", activePoolAddress, "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, 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 + } + + if !r.CanSwapFrom { + res.FlagSet = append(res.FlagSet, flag_incorrect_voucher) + res.Content = l.Get( + "%s is not in the KENYA ROLA POOL. Please update your voucher and try again.", + activeSym, + ) + return res, nil + } + + res.FlagReset = append(res.FlagReset, flag_incorrect_voucher) + + // call the api using the activePoolAddress and publicKey to get a list of SwapToSymbolsData + swapToList, err := h.accountService.GetPoolSwappableVouchers(ctx, string(activePoolAddress), string(publicKey)) + 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 vouchers + if len(swapToList) == 0 { + return res, nil + } + + data := store.ProcessVouchers(swapToList) + + // Store all to list voucher 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) + res.FlagReset = append(res.FlagSet, 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 + } + + // 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 + 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 FetchTransactions", "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 + } + + finalAmountStr, err := store.ParseAndScaleAmount(inputStr, 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 + } + + // 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.", + swapAmountStr, + swapData.ActiveSwapFromSym, + swapData.ActiveSwapToSym, + ) + + res.FlagReset = append(res.FlagReset, flag_account_authorized) + return res, nil +} diff --git a/handlers/local.go b/handlers/local.go index 69f5883..ef96b20 100644 --- a/handlers/local.go +++ b/handlers/local.go @@ -130,7 +130,10 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountService) ls.DbRs.AddLocalFunc("confirm_new_alias", appHandlers.ConfirmNewAlias) ls.DbRs.AddLocalFunc("check_account_created", appHandlers.CheckAccountCreated) ls.DbRs.AddLocalFunc("reset_api_call_failure", appHandlers.ResetApiCallFailure) - + ls.DbRs.AddLocalFunc("swap_to_list", appHandlers.LoadSwapToList) + ls.DbRs.AddLocalFunc("swap_max_limit", appHandlers.SwapMaxLimit) + ls.DbRs.AddLocalFunc("swap_preview", appHandlers.SwapPreview) + ls.DbRs.AddLocalFunc("initiate_swap", appHandlers.InitiateSwap) ls.first = appHandlers.Init return appHandlers, nil diff --git a/services/registration/api_failure b/services/registration/api_failure index 915b7fc..47583ac 100644 --- a/services/registration/api_failure +++ b/services/registration/api_failure @@ -1 +1 @@ -Failed to connect to the custodial service .Please try again. \ No newline at end of file +Failed to connect to the custodial service. Please try again. \ No newline at end of file diff --git a/services/registration/locale/swa/default.po b/services/registration/locale/swa/default.po index cac4737..5e5d456 100644 --- a/services/registration/locale/swa/default.po +++ b/services/registration/locale/swa/default.po @@ -31,6 +31,11 @@ msgstr "Salio la Kikundi: 0.00" msgid "Symbol: %s\nBalance: %s" msgstr "Sarafu: %s\nSalio: %s" +msgid "Your request has been sent. You will receive an SMS when your %s %s has been swapped for %s." +msgstr "Ombi lako limetumwa. Utapokea SMS wakati %s %s yako itakapobadilishwa kuwa %s." msgid "%s balance: %s\n" -msgstr "%s salio: %s\n" \ No newline at end of file +msgstr "%s salio: %s\n" + +msgid "%s is not in the KENYA ROLA POOL. Please update your voucher and try again." +msgstr "%s haipo kwenye BWAWA LA KENYA ROLA. Tafadhali badilisha sarafu yako na ujaribu tena." \ No newline at end of file diff --git a/services/registration/low_swap_amount b/services/registration/low_swap_amount new file mode 100644 index 0000000..d3e50f7 --- /dev/null +++ b/services/registration/low_swap_amount @@ -0,0 +1 @@ +Available amount {{.swap_max_limit}} is too low, please try again: \ No newline at end of file diff --git a/services/registration/low_swap_amount.vis b/services/registration/low_swap_amount.vis new file mode 100644 index 0000000..336ccde --- /dev/null +++ b/services/registration/low_swap_amount.vis @@ -0,0 +1,6 @@ +MAP swap_max_limit +MOUT retry 1 +MOUT quit 9 +HALT +INCMP _ 1 +INCMP quit 9 diff --git a/services/registration/low_swap_amount_swa b/services/registration/low_swap_amount_swa new file mode 100644 index 0000000..2d11dd9 --- /dev/null +++ b/services/registration/low_swap_amount_swa @@ -0,0 +1 @@ +Kiasi kinachopatikana {{.swap_max_limit}} ni cha chini sana, tafadhali jaribu tena: \ No newline at end of file diff --git a/services/registration/main.vis b/services/registration/main.vis index efa8b2a..0eb9fc9 100644 --- a/services/registration/main.vis +++ b/services/registration/main.vis @@ -8,14 +8,16 @@ RELOAD check_balance CATCH api_failure flag_api_call_error 1 MAP check_balance MOUT send 1 -MOUT vouchers 2 -MOUT account 3 -MOUT help 4 +MOUT swap 2 +MOUT vouchers 3 +MOUT account 4 +MOUT help 5 MOUT quit 9 HALT INCMP send 1 -INCMP my_vouchers 2 -INCMP my_account 3 -INCMP help 4 +INCMP swap_to_list 2 +INCMP my_vouchers 3 +INCMP my_account 4 +INCMP help 5 INCMP quit 9 INCMP . * diff --git a/services/registration/missing_voucher b/services/registration/missing_voucher new file mode 100644 index 0000000..8b67f26 --- /dev/null +++ b/services/registration/missing_voucher @@ -0,0 +1 @@ +{{.swap_to_list}} \ No newline at end of file diff --git a/services/registration/missing_voucher.vis b/services/registration/missing_voucher.vis new file mode 100644 index 0000000..cf01c5b --- /dev/null +++ b/services/registration/missing_voucher.vis @@ -0,0 +1,6 @@ +MAP swap_to_list +MOUT back 0 +MOUT quit 9 +HALT +INCMP ^ 0 +INCMP quit 9 diff --git a/services/registration/missing_voucher_swa b/services/registration/missing_voucher_swa new file mode 100644 index 0000000..8b67f26 --- /dev/null +++ b/services/registration/missing_voucher_swa @@ -0,0 +1 @@ +{{.swap_to_list}} \ No newline at end of file diff --git a/services/registration/pp.csv b/services/registration/pp.csv index ff26614..fa67938 100644 --- a/services/registration/pp.csv +++ b/services/registration/pp.csv @@ -31,4 +31,7 @@ flag,flag_back_set,37,this is set when it is a back navigation flag,flag_account_blocked,38,this is set when an account has been blocked after the allowed incorrect PIN attempts have been exceeded flag,flag_invalid_pin,39,this is set when the given PIN is invalid(is less than or more than 4 digits) flag,flag_alias_set,40,this is set when an account alias has been assigned to a user -flag,flag_account_pin_reset,41,this is set on an account when an admin triggers a PIN reset for them +flag,flag_account_pin_reset,41,this is set on an account when an admin triggers a PIN reset for themflag,flag_incorrect_pool,39,this is set when the user selects an invalid pool +flag,flag_incorrect_pool,42,this is set when the user selects an invalid pool +flag,flag_low_swap_amount,43,this is set when the swap max limit is less than 0.1 + diff --git a/services/registration/swap_initiated.vis b/services/registration/swap_initiated.vis new file mode 100644 index 0000000..69ffc6d --- /dev/null +++ b/services/registration/swap_initiated.vis @@ -0,0 +1,3 @@ +LOAD reset_incorrect 6 +LOAD initiate_swap 0 +HALT diff --git a/services/registration/swap_limit b/services/registration/swap_limit new file mode 100644 index 0000000..4c9797c --- /dev/null +++ b/services/registration/swap_limit @@ -0,0 +1 @@ +{{.swap_max_limit}} \ No newline at end of file diff --git a/services/registration/swap_limit.vis b/services/registration/swap_limit.vis new file mode 100644 index 0000000..b96f1f5 --- /dev/null +++ b/services/registration/swap_limit.vis @@ -0,0 +1,6 @@ +RELOAD swap_max_limit +MAP swap_max_limit +MOUT back 0 +HALT +INCMP _ 0 +INCMP swap_preview * diff --git a/services/registration/swap_limit_swa b/services/registration/swap_limit_swa new file mode 100644 index 0000000..4c9797c --- /dev/null +++ b/services/registration/swap_limit_swa @@ -0,0 +1 @@ +{{.swap_max_limit}} \ No newline at end of file diff --git a/services/registration/swap_menu b/services/registration/swap_menu new file mode 100644 index 0000000..a5daabf --- /dev/null +++ b/services/registration/swap_menu @@ -0,0 +1 @@ +Swap \ No newline at end of file diff --git a/services/registration/swap_preview b/services/registration/swap_preview new file mode 100644 index 0000000..fb008e4 --- /dev/null +++ b/services/registration/swap_preview @@ -0,0 +1,3 @@ +{{.swap_preview}} + +Please enter your PIN to confirm: \ No newline at end of file diff --git a/services/registration/swap_preview.vis b/services/registration/swap_preview.vis new file mode 100644 index 0000000..901de26 --- /dev/null +++ b/services/registration/swap_preview.vis @@ -0,0 +1,12 @@ +LOAD swap_preview 0 +MAP swap_preview +MOUT back 0 +MOUT quit 9 +LOAD authorize_account 6 +HALT +RELOAD authorize_account +CATCH incorrect_pin flag_incorrect_pin 1 +CATCH . flag_account_authorized 0 +INCMP _ 0 +INCMP quit 9 +INCMP swap_initiated * diff --git a/services/registration/swap_preview_swa b/services/registration/swap_preview_swa new file mode 100644 index 0000000..4c6d567 --- /dev/null +++ b/services/registration/swap_preview_swa @@ -0,0 +1,3 @@ +{{.swap_preview}} + +Tafadhali weka PIN yako kudhibitisha: \ No newline at end of file diff --git a/services/registration/swap_to_list b/services/registration/swap_to_list new file mode 100644 index 0000000..86f56c3 --- /dev/null +++ b/services/registration/swap_to_list @@ -0,0 +1,2 @@ +Select number or symbol to swap TO: +{{.swap_to_list}} \ No newline at end of file diff --git a/services/registration/swap_to_list.vis b/services/registration/swap_to_list.vis new file mode 100644 index 0000000..a2d3bea --- /dev/null +++ b/services/registration/swap_to_list.vis @@ -0,0 +1,12 @@ +LOAD swap_to_list 0 +RELOAD swap_to_list +MAP swap_to_list +CATCH missing_voucher flag_incorrect_voucher 1 +MOUT back 0 +HALT +LOAD swap_max_limit 64 +RELOAD swap_max_limit +CATCH . flag_incorrect_voucher 1 +CATCH low_swap_amount flag_low_swap_amount 1 +INCMP _ 0 +INCMP swap_limit * diff --git a/services/registration/swap_to_list_swa b/services/registration/swap_to_list_swa new file mode 100644 index 0000000..42da7d3 --- /dev/null +++ b/services/registration/swap_to_list_swa @@ -0,0 +1,2 @@ +Chagua nambari au ishara ya sarafu kubadilisha KWENDA: +{{.swap_to_list}} \ No newline at end of file diff --git a/store/db/db.go b/store/db/db.go index 517d0a0..e6e1eef 100644 --- a/store/db/db.go +++ b/store/db/db.go @@ -67,6 +67,24 @@ const ( DATA_SUGGESTED_ALIAS //Key used to store a value of 1 for a user to reset their own PIN once they access the menu. DATA_SELF_PIN_RESET + // Holds the active pool contract address for the swap + DATA_ACTIVE_POOL_ADDRESS + // Currently active swap from symbol for the swap + DATA_ACTIVE_SWAP_FROM_SYM + // Currently active swap from decimal count for the swap + DATA_ACTIVE_SWAP_FROM_DECIMAL + // Holds the active swap from contract address for the swap + DATA_ACTIVE_SWAP_FROM_ADDRESS + // Currently active swap from to for the swap + DATA_ACTIVE_SWAP_TO_SYM + // Currently active swap to decimal count for the swap + DATA_ACTIVE_SWAP_TO_DECIMAL + // Holds the active pool contract address for the swap + DATA_ACTIVE_SWAP_TO_ADDRESS + // Holds the max swap amount for the swap + DATA_ACTIVE_SWAP_MAX_AMOUNT + // Holds the active swap amount for the swap + DATA_ACTIVE_SWAP_AMOUNT ) const ( @@ -105,6 +123,31 @@ const ( DATA_TRANSACTIONS = 1024 + iota ) +const ( + // List of voucher symbols in the top pools context. + DATA_POOL_NAMES = 2048 + iota + // List of symbols in the top pools context. + DATA_POOL_SYMBOLS + // List of contact addresses in the top pools context + DATA_POOL_ADDRESSES + // List of swap from voucher symbols in the user context. + DATA_POOL_FROM_SYMBOLS + // List of swap from balances for vouchers valid in the pools context. + DATA_POOL_FROM_BALANCES + // List of swap from decimal counts for vouchers valid in the pools context. + DATA_POOL_FROM_DECIMALS + // List of swap from EVM addresses for vouchers valid in the pools context. + DATA_POOL_FROM_ADDRESSES + // List of swap to voucher symbols in the user context. + DATA_POOL_TO_SYMBOLS + // List of swap to balances for vouchers valid in the pools context. + DATA_POOL_TO_BALANCES + // List of swap to decimal counts for vouchers valid in the pools context. + DATA_POOL_TO_DECIMALS + // List of swap to EVM addresses for vouchers valid in the pools context. + DATA_POOL_TO_ADDRESSES +) + var ( logg = logging.NewVanilla().WithDomain("urdt-common") ) diff --git a/store/pools.go b/store/pools.go new file mode 100644 index 0000000..1e01329 --- /dev/null +++ b/store/pools.go @@ -0,0 +1,93 @@ +package store + +import ( + "context" + "fmt" + "strings" + + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" + dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" +) + +// PoolsMetadata helps organize data fields +type PoolsMetadata struct { + PoolNames string + PoolSymbols string + PoolContractAdrresses string +} + +// ProcessPools converts pools into formatted strings +func ProcessPools(pools []dataserviceapi.PoolDetails) PoolsMetadata { + var data PoolsMetadata + var poolNames, poolSymbols, poolContractAdrresses []string + + for i, p := range pools { + poolNames = append(poolNames, fmt.Sprintf("%d:%s", i+1, p.PoolName)) + poolSymbols = append(poolSymbols, fmt.Sprintf("%d:%s", i+1, p.PoolSymbol)) + poolContractAdrresses = append(poolContractAdrresses, fmt.Sprintf("%d:%s", i+1, p.PoolContractAdrress)) + } + + data.PoolNames = strings.Join(poolNames, "\n") + data.PoolSymbols = strings.Join(poolSymbols, "\n") + data.PoolContractAdrresses = strings.Join(poolContractAdrresses, "\n") + + return data +} + +// GetPoolData retrieves and matches pool data +// if no match is found, it fetches the API with the symbol +func GetPoolData(ctx context.Context, store DataStore, sessionId string, input string) (*dataserviceapi.PoolDetails, error) { + keys := []storedb.DataTyp{ + storedb.DATA_POOL_NAMES, + storedb.DATA_POOL_SYMBOLS, + storedb.DATA_POOL_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) + } + + name, symbol, address := MatchPool(input, + data[storedb.DATA_POOL_NAMES], + data[storedb.DATA_POOL_SYMBOLS], + data[storedb.DATA_POOL_ADDRESSES], + ) + + if symbol == "" { + return nil, nil + } + + return &dataserviceapi.PoolDetails{ + PoolName: string(name), + PoolSymbol: string(symbol), + PoolContractAdrress: string(address), + }, nil +} + +// MatchPool finds the matching pool name, symbol and pool contract address based on the input. +func MatchPool(input, names, symbols, addresses string) (name, symbol, address string) { + nameList := strings.Split(names, "\n") + symList := strings.Split(symbols, "\n") + addrList := strings.Split(addresses, "\n") + + 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(nameList) { + name = strings.SplitN(nameList[i], ":", 2)[1] + } + if i < len(addrList) { + address = strings.SplitN(addrList[i], ":", 2)[1] + } + break + } + } + return +} diff --git a/store/swap.go b/store/swap.go new file mode 100644 index 0000000..fc0a569 --- /dev/null +++ b/store/swap.go @@ -0,0 +1,187 @@ +package store + +import ( + "context" + "errors" + "fmt" + "reflect" + + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" + dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" +) + +type SwapData struct { + PublicKey string + ActivePoolAddress string + ActiveSwapFromSym string + ActiveSwapFromDecimal string + ActiveSwapFromAddress string + ActiveSwapToSym string + ActiveSwapToAddress string +} + +type SwapPreviewData struct { + PublicKey string + ActiveSwapMaxAmount string + ActiveSwapFromDecimal string + ActivePoolAddress string + ActiveSwapFromAddress string + ActiveSwapFromSym string + ActiveSwapToAddress string + ActiveSwapToSym string + ActiveSwapToDecimal string +} + +func ReadSwapData(ctx context.Context, store DataStore, sessionId string) (SwapData, error) { + data := SwapData{} + fieldToKey := map[string]storedb.DataTyp{ + "PublicKey": storedb.DATA_PUBLIC_KEY, + "ActivePoolAddress": storedb.DATA_ACTIVE_POOL_ADDRESS, + "ActiveSwapFromSym": storedb.DATA_ACTIVE_SYM, + "ActiveSwapFromDecimal": storedb.DATA_ACTIVE_DECIMAL, + "ActiveSwapFromAddress": storedb.DATA_ACTIVE_ADDRESS, + "ActiveSwapToSym": storedb.DATA_ACTIVE_SWAP_TO_SYM, + "ActiveSwapToAddress": storedb.DATA_ACTIVE_SWAP_TO_ADDRESS, + } + + v := reflect.ValueOf(&data).Elem() + for fieldName, key := range fieldToKey { + field := v.FieldByName(fieldName) + if !field.IsValid() || !field.CanSet() { + return data, errors.New("invalid struct field: " + fieldName) + } + + value, err := ReadStringEntry(ctx, store, sessionId, key) + if err != nil { + return data, err + } + field.SetString(value) + } + + return data, nil +} + +func ReadSwapPreviewData(ctx context.Context, store DataStore, sessionId string) (SwapPreviewData, error) { + data := SwapPreviewData{} + fieldToKey := map[string]storedb.DataTyp{ + "PublicKey": storedb.DATA_PUBLIC_KEY, + "ActiveSwapMaxAmount": storedb.DATA_ACTIVE_SWAP_MAX_AMOUNT, + "ActiveSwapFromDecimal": storedb.DATA_ACTIVE_DECIMAL, + "ActivePoolAddress": storedb.DATA_ACTIVE_POOL_ADDRESS, + "ActiveSwapFromAddress": storedb.DATA_ACTIVE_ADDRESS, + "ActiveSwapFromSym": storedb.DATA_ACTIVE_SYM, + "ActiveSwapToAddress": storedb.DATA_ACTIVE_SWAP_TO_ADDRESS, + "ActiveSwapToSym": storedb.DATA_ACTIVE_SWAP_TO_SYM, + "ActiveSwapToDecimal": storedb.DATA_ACTIVE_SWAP_TO_DECIMAL, + } + + v := reflect.ValueOf(&data).Elem() + for fieldName, key := range fieldToKey { + field := v.FieldByName(fieldName) + if !field.IsValid() || !field.CanSet() { + return data, errors.New("invalid struct field: " + fieldName) + } + + value, err := ReadStringEntry(ctx, store, sessionId, key) + if err != nil { + return data, err + } + field.SetString(value) + } + + return data, nil +} + +// GetSwapFromVoucherData retrieves and matches swap from voucher data +func GetSwapFromVoucherData(ctx context.Context, store DataStore, sessionId string, input string) (*dataserviceapi.TokenHoldings, error) { + keys := []storedb.DataTyp{ + storedb.DATA_POOL_FROM_SYMBOLS, + storedb.DATA_POOL_FROM_BALANCES, + storedb.DATA_POOL_FROM_DECIMALS, + storedb.DATA_POOL_FROM_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_POOL_FROM_SYMBOLS], + data[storedb.DATA_POOL_FROM_BALANCES], + data[storedb.DATA_POOL_FROM_DECIMALS], + data[storedb.DATA_POOL_FROM_ADDRESSES], + ) + + if symbol == "" { + return nil, nil + } + + return &dataserviceapi.TokenHoldings{ + TokenSymbol: string(symbol), + Balance: string(balance), + TokenDecimals: string(decimal), + ContractAddress: string(address), + }, nil +} + +// GetSwapToVoucherData retrieves and matches voucher data +func GetSwapToVoucherData(ctx context.Context, store DataStore, sessionId string, input string) (*dataserviceapi.TokenHoldings, error) { + keys := []storedb.DataTyp{ + storedb.DATA_POOL_TO_SYMBOLS, + storedb.DATA_POOL_TO_BALANCES, + storedb.DATA_POOL_TO_DECIMALS, + storedb.DATA_POOL_TO_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_POOL_TO_SYMBOLS], + data[storedb.DATA_POOL_TO_BALANCES], + data[storedb.DATA_POOL_TO_DECIMALS], + data[storedb.DATA_POOL_TO_ADDRESSES], + ) + + if symbol == "" { + return nil, nil + } + + return &dataserviceapi.TokenHoldings{ + TokenSymbol: string(symbol), + Balance: string(balance), + TokenDecimals: string(decimal), + ContractAddress: string(address), + }, nil +} + +// UpdateSwapToVoucherData updates the active swap to voucher data in the DataStore. +func UpdateSwapToVoucherData(ctx context.Context, store DataStore, sessionId string, data *dataserviceapi.TokenHoldings) error { + logg.TraceCtxf(ctx, "dtal", "data", data) + // Active swap to voucher data entries + activeEntries := map[storedb.DataTyp][]byte{ + storedb.DATA_ACTIVE_SWAP_TO_SYM: []byte(data.TokenSymbol), + storedb.DATA_ACTIVE_SWAP_TO_DECIMAL: []byte(data.TokenDecimals), + storedb.DATA_ACTIVE_SWAP_TO_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 +} diff --git a/store/swap_test.go b/store/swap_test.go new file mode 100644 index 0000000..179eeee --- /dev/null +++ b/store/swap_test.go @@ -0,0 +1,146 @@ +package store + +import ( + "testing" + + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" + "github.com/alecthomas/assert/v2" +) + +func TestReadSwapData(t *testing.T) { + sessionId := "session123" + publicKey := "0X13242618721" + ctx, store := InitializeTestDb(t) + + // Test swap data + swapData := map[storedb.DataTyp]string{ + storedb.DATA_PUBLIC_KEY: publicKey, + storedb.DATA_ACTIVE_POOL_ADDRESS: "0x48a953cA5cf5298bc6f6Af3C608351f537AAcb9e", + storedb.DATA_ACTIVE_SWAP_FROM_SYM: "AMANI", + storedb.DATA_ACTIVE_SWAP_FROM_DECIMAL: "6", + storedb.DATA_ACTIVE_SWAP_FROM_ADDRESS: "0xc7B78Ac9ACB9E025C8234621FC515bC58179dEAe", + storedb.DATA_ACTIVE_SWAP_TO_SYM: "cUSD", + storedb.DATA_ACTIVE_SWAP_TO_ADDRESS: "0x765DE816845861e75A25fCA122bb6898B8B1282a", + } + + // Store the data + for key, value := range swapData { + if err := store.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil { + t.Fatal(err) + } + } + + expectedResult := SwapData{ + PublicKey: "0X13242618721", + ActivePoolAddress: "0x48a953cA5cf5298bc6f6Af3C608351f537AAcb9e", + ActiveSwapFromSym: "AMANI", + ActiveSwapFromDecimal: "6", + ActiveSwapFromAddress: "0xc7B78Ac9ACB9E025C8234621FC515bC58179dEAe", + ActiveSwapToSym: "cUSD", + ActiveSwapToAddress: "0x765DE816845861e75A25fCA122bb6898B8B1282a", + } + + data, err := ReadSwapData(ctx, store, sessionId) + + assert.NoError(t, err) + assert.Equal(t, expectedResult, data) +} + +func TestReadSwapPreviewData(t *testing.T) { + sessionId := "session123" + publicKey := "0X13242618721" + ctx, store := InitializeTestDb(t) + + // Test swap preview data + swapPreviewData := map[storedb.DataTyp]string{ + storedb.DATA_PUBLIC_KEY: publicKey, + storedb.DATA_ACTIVE_SWAP_MAX_AMOUNT: "1339482", + storedb.DATA_ACTIVE_SWAP_FROM_DECIMAL: "6", + storedb.DATA_ACTIVE_POOL_ADDRESS: "0x48a953cA5cf5298bc6f6Af3C608351f537AAcb9e", + storedb.DATA_ACTIVE_SWAP_FROM_ADDRESS: "0xc7B78Ac9ACB9E025C8234621FC515bC58179dEAe", + storedb.DATA_ACTIVE_SWAP_FROM_SYM: "AMANI", + storedb.DATA_ACTIVE_SWAP_TO_ADDRESS: "0x765DE816845861e75A25fCA122bb6898B8B1282a", + storedb.DATA_ACTIVE_SWAP_TO_SYM: "cUSD", + storedb.DATA_ACTIVE_SWAP_TO_DECIMAL: "18", + } + + // Store the data + for key, value := range swapPreviewData { + if err := store.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil { + t.Fatal(err) + } + } + + expectedResult := SwapPreviewData{ + PublicKey: "0X13242618721", + ActiveSwapMaxAmount: "1339482", + ActiveSwapFromDecimal: "6", + ActivePoolAddress: "0x48a953cA5cf5298bc6f6Af3C608351f537AAcb9e", + ActiveSwapFromAddress: "0xc7B78Ac9ACB9E025C8234621FC515bC58179dEAe", + ActiveSwapFromSym: "AMANI", + ActiveSwapToAddress: "0x765DE816845861e75A25fCA122bb6898B8B1282a", + ActiveSwapToSym: "cUSD", + ActiveSwapToDecimal: "18", + } + + data, err := ReadSwapPreviewData(ctx, store, sessionId) + + assert.NoError(t, err) + assert.Equal(t, expectedResult, data) +} + +func TestGetSwapFromVoucherData(t *testing.T) { + sessionId := "session123" + ctx, store := InitializeTestDb(t) + + // Test pool swap data + mockData := map[storedb.DataTyp][]byte{ + storedb.DATA_POOL_FROM_SYMBOLS: []byte("1:AMANI\n2:AMUA"), + storedb.DATA_POOL_FROM_BALANCES: []byte("1:\n2:"), + storedb.DATA_POOL_FROM_DECIMALS: []byte("1:6\n2:4"), + storedb.DATA_POOL_FROM_ADDRESSES: []byte("1:0xc7B78Ac9ACB9E025C8234621FC515bC58179dEAe\n2:0xF0C3C7581b8b96B59a97daEc8Bd48247cE078674"), + } + + // Put the data + for key, value := range mockData { + if err := store.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil { + t.Fatal(err) + } + } + + result, err := GetSwapFromVoucherData(ctx, store, sessionId, "1") + + assert.NoError(t, err) + assert.Equal(t, "AMANI", result.TokenSymbol) + assert.Equal(t, "", result.Balance) + assert.Equal(t, "6", result.TokenDecimals) + assert.Equal(t, "0xc7B78Ac9ACB9E025C8234621FC515bC58179dEAe", result.ContractAddress) +} + +func TestGetSwapToVoucherData(t *testing.T) { + sessionId := "session123" + ctx, store := InitializeTestDb(t) + + // Test pool swap data + mockData := map[storedb.DataTyp][]byte{ + storedb.DATA_POOL_TO_SYMBOLS: []byte("1:cUSD\n2:AMUA"), + storedb.DATA_POOL_TO_BALANCES: []byte("1:\n2:"), + storedb.DATA_POOL_TO_DECIMALS: []byte("1:6\n2:4"), + storedb.DATA_POOL_TO_ADDRESSES: []byte("1:0xc7B78Ac9ACB9E025C8234621\n2:0xF0C3C7581b8b96B59a97daEc8Bd48247cE078674"), + } + + // Put the data + for key, value := range mockData { + if err := store.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil { + t.Fatal(err) + } + } + + result, err := GetSwapToVoucherData(ctx, store, sessionId, "1") + + assert.NoError(t, err) + assert.Equal(t, "cUSD", result.TokenSymbol) + assert.Equal(t, "", result.Balance) + assert.Equal(t, "6", result.TokenDecimals) + assert.Equal(t, "0xc7B78Ac9ACB9E025C8234621", result.ContractAddress) +} diff --git a/store/tokens.go b/store/tokens.go index 7c3ad0c..a7770c7 100644 --- a/store/tokens.go +++ b/store/tokens.go @@ -64,7 +64,7 @@ func ReadTransactionData(ctx context.Context, store DataStore, sessionId string) return data, errors.New("invalid struct field: " + fieldName) } - value, err := readStringEntry(ctx, store, sessionId, key) + value, err := ReadStringEntry(ctx, store, sessionId, key) if err != nil { return data, err } @@ -74,7 +74,7 @@ func ReadTransactionData(ctx context.Context, store DataStore, sessionId string) return data, nil } -func readStringEntry(ctx context.Context, store DataStore, sessionId string, key storedb.DataTyp) (string, error) { +func ReadStringEntry(ctx context.Context, store DataStore, sessionId string, key storedb.DataTyp) (string, error) { entry, err := store.ReadEntry(ctx, sessionId, key) if err != nil { return "", err From 2d8333b89a815f6610e4d6d3d759ee29a130b3e3 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Tue, 20 May 2025 10:19:46 +0300 Subject: [PATCH 02/25] add the new env variables --- .env.example | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.env.example b/.env.example index 7287e7c..bae96b5 100644 --- a/.env.example +++ b/.env.example @@ -21,3 +21,10 @@ LANGUAGES=eng, swa #Alias search domains ALIAS_SEARCH_DOMAINS=sarafu.local, sarafu.eth + +#Pool swap +DEFAULT_POOL_NAME="Kenya ROLA Pool" +DEFAULT_POOL_SYMBOL=ROLA +DEFAULT_POOL_CONTRACT_ADDRESS=0x48a953cA5cf5298bc6f6Af3C608351f537AAcb9e +DEFAULT_LIMITER_ADDRESS= +DEFAULT_VOUCHER_REGISTRY= From cf237c592ae7b5305a585c1223e265ccdbb6039d Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Tue, 20 May 2025 10:25:27 +0300 Subject: [PATCH 03/25] add the pool variables to the config --- config/config.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/config/config.go b/config/config.go index 863e418..9e8412e 100644 --- a/config/config.go +++ b/config/config.go @@ -25,7 +25,7 @@ const ( defaultSSHHost string = "127.0.0.1" defaultSSHPort uint = 7122 defaultHTTPHost string = "127.0.0.1" - defaultHTTPPort uint = 7123 + defaultHTTPPort uint = 7123 defaultDomain = "sarafu.local" ) @@ -52,7 +52,6 @@ func SearchDomains() []string { return ParsedDomains } - func Language() string { return viseconfig.DefaultLanguage } @@ -76,3 +75,15 @@ func PortSSH() uint { func ATEndpoint() string { return env.GetEnv("AT_ENDPOINT", "/") } + +func DefaultPoolAddress() string { + return env.GetEnv("DEFAULT_POOL_CONTRACT_ADDRESS", "") +} + +func DefaultPoolName() string { + return env.GetEnv("DEFAULT_POOL_NAME", "") +} + +func DefaultPoolSymbol() string { + return env.GetEnv("DEFAULT_POOL_SYMBOL", "") +} From 5d8fee470a7aa34c73efdfc6d0c67230ec867086 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Tue, 20 May 2025 10:26:01 +0300 Subject: [PATCH 04/25] use the default pool variables --- handlers/application/menuhandler.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/handlers/application/menuhandler.go b/handlers/application/menuhandler.go index 40abbe5..65fd259 100644 --- a/handlers/application/menuhandler.go +++ b/handlers/application/menuhandler.go @@ -2738,9 +2738,9 @@ func (h *MenuHandlers) LoadSwapToList(ctx context.Context, sym string, input []b } defaultPool := dataserviceapi.PoolDetails{ - PoolName: "Kenya ROLA Pool", - PoolSymbol: "ROLA", - PoolContractAdrress: "0x48a953cA5cf5298bc6f6Af3C608351f537AAcb9e", + PoolName: config.DefaultPoolName(), + PoolSymbol: config.DefaultPoolSymbol(), + PoolContractAdrress: config.DefaultPoolAddress(), LimiterAddress: "", VoucherRegistry: "", } @@ -2765,8 +2765,9 @@ func (h *MenuHandlers) LoadSwapToList(ctx context.Context, sym string, input []b if !r.CanSwapFrom { res.FlagSet = append(res.FlagSet, flag_incorrect_voucher) res.Content = l.Get( - "%s is not in the KENYA ROLA POOL. Please update your voucher and try again.", + "%s is not in %s. Please update your voucher and try again.", activeSym, + config.DefaultPoolName(), ) return res, nil } From d3ad3b2db6d82c4cba275a219c022b6f642a8b3a Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Tue, 20 May 2025 10:26:28 +0300 Subject: [PATCH 05/25] update the template and remove hardcoded values --- services/registration/locale/swa/default.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/registration/locale/swa/default.po b/services/registration/locale/swa/default.po index 5e5d456..fbd92fb 100644 --- a/services/registration/locale/swa/default.po +++ b/services/registration/locale/swa/default.po @@ -37,5 +37,5 @@ msgstr "Ombi lako limetumwa. Utapokea SMS wakati %s %s yako itakapobadilishwa ku msgid "%s balance: %s\n" msgstr "%s salio: %s\n" -msgid "%s is not in the KENYA ROLA POOL. Please update your voucher and try again." -msgstr "%s haipo kwenye BWAWA LA KENYA ROLA. Tafadhali badilisha sarafu yako na ujaribu tena." \ No newline at end of file +msgid "%s is not in %s. Please update your voucher and try again." +msgstr "%s haipo kwenye %s. Tafadhali badilisha sarafu yako na ujaribu tena." \ No newline at end of file From b428e2e22d9ac60a0f5c7768384a5f525240b8e5 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Tue, 20 May 2025 14:02:41 +0300 Subject: [PATCH 06/25] add log for the CheckTokenInPool --- handlers/application/menuhandler.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/handlers/application/menuhandler.go b/handlers/application/menuhandler.go index 65fd259..12926f8 100644 --- a/handlers/application/menuhandler.go +++ b/handlers/application/menuhandler.go @@ -2762,6 +2762,8 @@ func (h *MenuHandlers) LoadSwapToList(ctx context.Context, sym string, input []b return res, err } + logg.InfoCtxf(ctx, "CheckTokenInPool", "response", r, "active_pool_address", activePoolAddress, "address", activeAddress) + if !r.CanSwapFrom { res.FlagSet = append(res.FlagSet, flag_incorrect_voucher) res.Content = l.Get( From 3ab76b3d2256544a9ea6d1d51e2864853d774644 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Tue, 20 May 2025 15:53:12 +0300 Subject: [PATCH 07/25] use updated sarafu-api with corrected JSON mapping for checkTokenInPool and getSwapFromTokenMaxLimit --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 0a488e4..d137521 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.23.4 require ( git.defalsify.org/vise.git v0.3.2-0.20250507172020-cb22240f1cb9 git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20250417111317-2953f4c2f32e - git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250517114512-050998ff82b1 + git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250520125035-81fbc2574c30 git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306 git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694 github.com/alecthomas/assert/v2 v2.2.2 diff --git a/go.sum b/go.sum index 14da9e9..89495eb 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,8 @@ git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250517113706- git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250517113706-ee434dba6980/go.mod h1:K/TPgZ4OhPHBQq2X0ab3JZs4YjiexzSURZcfHLs9Pf4= git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250517114512-050998ff82b1 h1:0hvILlGkZnXO8o7nZth4xu77vAS4zVQ6Ae0rb5x/Idg= git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250517114512-050998ff82b1/go.mod h1:K/TPgZ4OhPHBQq2X0ab3JZs4YjiexzSURZcfHLs9Pf4= +git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250520125035-81fbc2574c30 h1:iZVmR/4xSoxahtkMBXs/z1HUSSnr+x1m+9AxXIgTvb0= +git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250520125035-81fbc2574c30/go.mod h1:R7+mt27mhm4/LCutAHHv87pOsLxXEPO93KXnhP8H6W0= git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306 h1:Jo+yWysWw/N5BJQtAyEMN8ePVvAyPHv+JG4lQti+1N4= git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306/go.mod h1:FdLwYtzsjOIcDiW4uDgDYnB4Wdzq12uJUe0QHSSPbSo= git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694 h1:DjJlBSz0S13acft5XZDWk7ZYnzElym0xLMYEVgyNJ+E= From 169d85e2cb29a8a0d86f63823503e4b2d26adf04 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Wed, 21 May 2025 15:39:19 +0300 Subject: [PATCH 08/25] added the INCLUDE_STABLES_PARAM env variable --- .env.example | 1 + 1 file changed, 1 insertion(+) diff --git a/.env.example b/.env.example index bae96b5..f582e30 100644 --- a/.env.example +++ b/.env.example @@ -28,3 +28,4 @@ DEFAULT_POOL_SYMBOL=ROLA DEFAULT_POOL_CONTRACT_ADDRESS=0x48a953cA5cf5298bc6f6Af3C608351f537AAcb9e DEFAULT_LIMITER_ADDRESS= DEFAULT_VOUCHER_REGISTRY= +INCLUDE_STABLES_PARAM=false From 21da4b976bbf9100a67ee40e9a98c3687d885ef9 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Wed, 21 May 2025 15:45:17 +0300 Subject: [PATCH 09/25] use the updated sarafu-api that removes the publicKey from the GetPoolSwappableVouchers --- go.mod | 2 +- go.sum | 2 ++ handlers/application/menuhandler.go | 9 ++------- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index d137521..6a8ec8d 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.23.4 require ( git.defalsify.org/vise.git v0.3.2-0.20250507172020-cb22240f1cb9 git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20250417111317-2953f4c2f32e - git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250520125035-81fbc2574c30 + git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250521124206-343d30a2f27c git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306 git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694 github.com/alecthomas/assert/v2 v2.2.2 diff --git a/go.sum b/go.sum index 89495eb..b2011a6 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,8 @@ git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250517114512- git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250517114512-050998ff82b1/go.mod h1:K/TPgZ4OhPHBQq2X0ab3JZs4YjiexzSURZcfHLs9Pf4= git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250520125035-81fbc2574c30 h1:iZVmR/4xSoxahtkMBXs/z1HUSSnr+x1m+9AxXIgTvb0= git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250520125035-81fbc2574c30/go.mod h1:R7+mt27mhm4/LCutAHHv87pOsLxXEPO93KXnhP8H6W0= +git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250521124206-343d30a2f27c h1:c2cA5hcYnLgs7kR4fF5tp42W6VQClTukbfKoaeskZu0= +git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250521124206-343d30a2f27c/go.mod h1:R7+mt27mhm4/LCutAHHv87pOsLxXEPO93KXnhP8H6W0= git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306 h1:Jo+yWysWw/N5BJQtAyEMN8ePVvAyPHv+JG4lQti+1N4= git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306/go.mod h1:FdLwYtzsjOIcDiW4uDgDYnB4Wdzq12uJUe0QHSSPbSo= git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694 h1:DjJlBSz0S13acft5XZDWk7ZYnzElym0xLMYEVgyNJ+E= diff --git a/handlers/application/menuhandler.go b/handlers/application/menuhandler.go index 12926f8..41c0996 100644 --- a/handlers/application/menuhandler.go +++ b/handlers/application/menuhandler.go @@ -2707,11 +2707,6 @@ func (h *MenuHandlers) LoadSwapToList(ctx context.Context, sym string, input []b } userStore := h.userdataStore - publicKey, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY) - if err != nil { - logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err) - return res, err - } // get the active address and symbol activeAddress, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS) @@ -2776,8 +2771,8 @@ func (h *MenuHandlers) LoadSwapToList(ctx context.Context, sym string, input []b res.FlagReset = append(res.FlagReset, flag_incorrect_voucher) - // call the api using the activePoolAddress and publicKey to get a list of SwapToSymbolsData - swapToList, err := h.accountService.GetPoolSwappableVouchers(ctx, string(activePoolAddress), string(publicKey)) + // 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) From d7987bf4603f122a4b50202644c606e85dc2ad37 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Wed, 21 May 2025 18:18:15 +0300 Subject: [PATCH 10/25] use the updated ussd-data-service and sarafu-api with TokenDetails --- go.mod | 4 ++-- go.sum | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 6a8ec8d..fffaced 100644 --- a/go.mod +++ b/go.mod @@ -5,12 +5,12 @@ go 1.23.4 require ( git.defalsify.org/vise.git v0.3.2-0.20250507172020-cb22240f1cb9 git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20250417111317-2953f4c2f32e - git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250521124206-343d30a2f27c + git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250521141246-6c3719e3b652 git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306 git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694 github.com/alecthomas/assert/v2 v2.2.2 github.com/gofrs/uuid v4.4.0+incompatible - github.com/grassrootseconomics/ussd-data-service v1.4.4-beta + github.com/grassrootseconomics/ussd-data-service v1.4.7-beta github.com/jackc/pgx/v5 v5.7.1 github.com/peteole/testdata-loader v0.3.0 github.com/stretchr/testify v1.9.0 diff --git a/go.sum b/go.sum index b2011a6..83dc9d8 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,8 @@ git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250520125035- git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250520125035-81fbc2574c30/go.mod h1:R7+mt27mhm4/LCutAHHv87pOsLxXEPO93KXnhP8H6W0= git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250521124206-343d30a2f27c h1:c2cA5hcYnLgs7kR4fF5tp42W6VQClTukbfKoaeskZu0= git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250521124206-343d30a2f27c/go.mod h1:R7+mt27mhm4/LCutAHHv87pOsLxXEPO93KXnhP8H6W0= +git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250521141246-6c3719e3b652 h1:MmUQRL6Fjq/jlL53h+cUbJ7WwQ+q/yNy/zT05c7bgNg= +git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250521141246-6c3719e3b652/go.mod h1:wKHPy1mpOCr9ahkRltwm1yi9qH/3m9xb8hMCX5C0L1o= git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306 h1:Jo+yWysWw/N5BJQtAyEMN8ePVvAyPHv+JG4lQti+1N4= git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306/go.mod h1:FdLwYtzsjOIcDiW4uDgDYnB4Wdzq12uJUe0QHSSPbSo= git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694 h1:DjJlBSz0S13acft5XZDWk7ZYnzElym0xLMYEVgyNJ+E= @@ -78,6 +80,8 @@ github.com/grassrootseconomics/ussd-data-service v1.4.0-beta h1:4fMd/3h2ZIhRg4Gd github.com/grassrootseconomics/ussd-data-service v1.4.0-beta/go.mod h1:9sGnorpKaK76FmOGXoh/xv7x5siSFNYdXxQo9BKW4DI= github.com/grassrootseconomics/ussd-data-service v1.4.4-beta h1:turlyo0i3OLj29mWpWNoB/3Qao8qEngT/5d1jDWTZE4= github.com/grassrootseconomics/ussd-data-service v1.4.4-beta/go.mod h1:9sGnorpKaK76FmOGXoh/xv7x5siSFNYdXxQo9BKW4DI= +github.com/grassrootseconomics/ussd-data-service v1.4.7-beta h1:yAe1YaOBsdxW2m20jnVU4F0kLmFr+mK/gHCWEdHmE90= +github.com/grassrootseconomics/ussd-data-service v1.4.7-beta/go.mod h1:9sGnorpKaK76FmOGXoh/xv7x5siSFNYdXxQo9BKW4DI= github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 h1:U4kkNYryi/qfbBF8gh7Vsbuz+cVmhf5kt6pE9bYYyLo= github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4/go.mod h1:zpZDgZFzeq9s0MIeB1P50NIEWDFFHSFBohI/NbaTD/Y= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= From 1cc1d00ffeb2fc70369b161f0fe0f4f6f003a76b Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Wed, 21 May 2025 18:19:23 +0300 Subject: [PATCH 11/25] added a function to ProcessTokens from the GetPoolSwappableVouchers --- store/vouchers.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/store/vouchers.go b/store/vouchers.go index 94ebc5a..6a4b42f 100644 --- a/store/vouchers.go +++ b/store/vouchers.go @@ -47,6 +47,24 @@ func ProcessVouchers(holdings []dataserviceapi.TokenHoldings) VoucherMetadata { return data } +// ProcessTokens converts swappable tokens into formatted strings +func ProcessTokens(holdings []dataserviceapi.TokenDetails) VoucherMetadata { + var data VoucherMetadata + var symbols, decimals, addresses []string + + for i, h := range holdings { + symbols = append(symbols, fmt.Sprintf("%d:%s", i+1, h.TokenSymbol)) + decimals = append(decimals, fmt.Sprintf("%d:%d", i+1, h.TokenDecimals)) + addresses = append(addresses, fmt.Sprintf("%d:%s", i+1, h.TokenAddress)) + } + + data.Symbols = strings.Join(symbols, "\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) From 043c79384c0b508ed02cbc089d0bbaea22190c9f Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Wed, 21 May 2025 18:39:45 +0300 Subject: [PATCH 12/25] correctly process TokenDetails --- store/swap.go | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/store/swap.go b/store/swap.go index fc0a569..c3f5a77 100644 --- a/store/swap.go +++ b/store/swap.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "reflect" + "strconv" storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" @@ -129,8 +130,8 @@ func GetSwapFromVoucherData(ctx context.Context, store DataStore, sessionId stri }, nil } -// GetSwapToVoucherData retrieves and matches voucher data -func GetSwapToVoucherData(ctx context.Context, store DataStore, sessionId string, input string) (*dataserviceapi.TokenHoldings, error) { +// GetSwapToVoucherData retrieves and matches token data +func GetSwapToVoucherData(ctx context.Context, store DataStore, sessionId string, input string) (*dataserviceapi.TokenDetails, error) { keys := []storedb.DataTyp{ storedb.DATA_POOL_TO_SYMBOLS, storedb.DATA_POOL_TO_BALANCES, @@ -147,7 +148,7 @@ func GetSwapToVoucherData(ctx context.Context, store DataStore, sessionId string data[key] = string(value) } - symbol, balance, decimal, address := MatchVoucher(input, + symbol, _, decimal, address := MatchVoucher(input, data[storedb.DATA_POOL_TO_SYMBOLS], data[storedb.DATA_POOL_TO_BALANCES], data[storedb.DATA_POOL_TO_DECIMALS], @@ -158,22 +159,27 @@ func GetSwapToVoucherData(ctx context.Context, store DataStore, sessionId string return nil, nil } - return &dataserviceapi.TokenHoldings{ - TokenSymbol: string(symbol), - Balance: string(balance), - TokenDecimals: string(decimal), - ContractAddress: string(address), + decimalInt, err := strconv.ParseUint(decimal, 0, 64) + if err != nil { + logg.ErrorCtxf(ctx, "Failed to parse decimal to Uint:", "sessionId", sessionId, "decimal", decimal, "error", err) + return nil, nil + } + + return &dataserviceapi.TokenDetails{ + TokenSymbol: string(symbol), + TokenDecimals: uint8(decimalInt), + TokenAddress: string(address), }, nil } // UpdateSwapToVoucherData updates the active swap to voucher data in the DataStore. -func UpdateSwapToVoucherData(ctx context.Context, store DataStore, sessionId string, data *dataserviceapi.TokenHoldings) error { - logg.TraceCtxf(ctx, "dtal", "data", data) +func UpdateSwapToVoucherData(ctx context.Context, store DataStore, sessionId string, data *dataserviceapi.TokenDetails) error { + logg.TraceCtxf(ctx, "UpdateSwapToVoucherData", "data", data) // Active swap to voucher data entries activeEntries := map[storedb.DataTyp][]byte{ storedb.DATA_ACTIVE_SWAP_TO_SYM: []byte(data.TokenSymbol), - storedb.DATA_ACTIVE_SWAP_TO_DECIMAL: []byte(data.TokenDecimals), - storedb.DATA_ACTIVE_SWAP_TO_ADDRESS: []byte(data.ContractAddress), + storedb.DATA_ACTIVE_SWAP_TO_DECIMAL: []byte{data.TokenDecimals}, + storedb.DATA_ACTIVE_SWAP_TO_ADDRESS: []byte(data.TokenAddress), } // Write active data From 91b57fece2bec62b8531ac37f19faae14a013e0f Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Wed, 21 May 2025 18:51:10 +0300 Subject: [PATCH 13/25] use the ProcessTokens function and remove the unused DATA_POOL_TO_BALANCES --- handlers/application/menuhandler.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/handlers/application/menuhandler.go b/handlers/application/menuhandler.go index 41c0996..9860d4f 100644 --- a/handlers/application/menuhandler.go +++ b/handlers/application/menuhandler.go @@ -2784,12 +2784,11 @@ func (h *MenuHandlers) LoadSwapToList(ctx context.Context, sym string, input []b return res, nil } - data := store.ProcessVouchers(swapToList) + data := store.ProcessTokens(swapToList) - // Store all to list voucher 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, } From cf6e105fb923aea243bf739b983b23aa5d567195 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Wed, 21 May 2025 18:51:41 +0300 Subject: [PATCH 14/25] catch the api call error flag --- services/registration/swap_limit.vis | 1 + services/registration/swap_preview.vis | 1 + 2 files changed, 2 insertions(+) diff --git a/services/registration/swap_limit.vis b/services/registration/swap_limit.vis index b96f1f5..a9fa737 100644 --- a/services/registration/swap_limit.vis +++ b/services/registration/swap_limit.vis @@ -1,4 +1,5 @@ RELOAD swap_max_limit +CATCH api_failure flag_api_call_error 1 MAP swap_max_limit MOUT back 0 HALT diff --git a/services/registration/swap_preview.vis b/services/registration/swap_preview.vis index 901de26..e7c2b97 100644 --- a/services/registration/swap_preview.vis +++ b/services/registration/swap_preview.vis @@ -1,5 +1,6 @@ LOAD swap_preview 0 MAP swap_preview +CATCH api_failure flag_api_call_error 1 MOUT back 0 MOUT quit 9 LOAD authorize_account 6 From fd4306002104c4e6378348bdc8d641cba12c723d Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Wed, 21 May 2025 18:52:05 +0300 Subject: [PATCH 15/25] update the api failure template --- services/registration/api_failure | 2 +- services/registration/api_failure_swa | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/services/registration/api_failure b/services/registration/api_failure index 47583ac..d795316 100644 --- a/services/registration/api_failure +++ b/services/registration/api_failure @@ -1 +1 @@ -Failed to connect to the custodial service. Please try again. \ No newline at end of file +Your request failed. Please try again later. \ No newline at end of file diff --git a/services/registration/api_failure_swa b/services/registration/api_failure_swa index fb24f2c..5645247 100644 --- a/services/registration/api_failure_swa +++ b/services/registration/api_failure_swa @@ -1 +1 @@ -Imeshindwa kuunganisha kwenye huduma ya uangalizi. Tafadhali jaribu tena. \ No newline at end of file +Ombi lako halikufaulu. Tafadhali jaribu tena baadaye. \ No newline at end of file From 0495472530b888f1e7d96ce82bb69fb75ccb9e86 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 22 May 2025 12:13:50 +0300 Subject: [PATCH 16/25] match the reset_incorrect_pin symbol to the function name --- devtools/lang/main.go | 2 +- handlers/local.go | 4 ++-- services/registration/change_language.vis | 2 +- services/registration/community_balance.vis | 2 +- services/registration/incorrect_pin | 2 +- services/registration/incorrect_pin.vis | 6 +++--- services/registration/incorrect_pin_swa | 2 +- services/registration/my_balance.vis | 2 +- services/registration/old_pin.vis | 2 +- services/registration/pin_management.vis | 2 +- services/registration/swap_initiated.vis | 2 +- services/registration/transaction_initiated.vis | 2 +- services/registration/view_profile.vis | 2 +- services/registration/voucher_set.vis | 2 +- 14 files changed, 17 insertions(+), 17 deletions(-) diff --git a/devtools/lang/main.go b/devtools/lang/main.go index 4dd3090..3ec4c95 100644 --- a/devtools/lang/main.go +++ b/devtools/lang/main.go @@ -15,7 +15,7 @@ import ( const ( changeHeadSrc = `LOAD reset_account_authorized 0 -LOAD reset_incorrect 0 +LOAD reset_incorrect_pin 0 CATCH incorrect_pin flag_incorrect_pin 1 CATCH pin_entry flag_account_authorized 0 ` diff --git a/handlers/local.go b/handlers/local.go index ef96b20..01c8800 100644 --- a/handlers/local.go +++ b/handlers/local.go @@ -91,7 +91,7 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountService) ls.DbRs.AddLocalFunc("get_recipient", appHandlers.GetRecipient) ls.DbRs.AddLocalFunc("get_sender", appHandlers.GetSender) ls.DbRs.AddLocalFunc("get_amount", appHandlers.GetAmount) - ls.DbRs.AddLocalFunc("reset_incorrect", appHandlers.ResetIncorrectPin) + ls.DbRs.AddLocalFunc("reset_incorrect_pin", appHandlers.ResetIncorrectPin) ls.DbRs.AddLocalFunc("save_firstname", appHandlers.SaveFirstname) ls.DbRs.AddLocalFunc("save_familyname", appHandlers.SaveFamilyname) ls.DbRs.AddLocalFunc("save_gender", appHandlers.SaveGender) @@ -130,7 +130,7 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountService) ls.DbRs.AddLocalFunc("confirm_new_alias", appHandlers.ConfirmNewAlias) ls.DbRs.AddLocalFunc("check_account_created", appHandlers.CheckAccountCreated) ls.DbRs.AddLocalFunc("reset_api_call_failure", appHandlers.ResetApiCallFailure) - ls.DbRs.AddLocalFunc("swap_to_list", appHandlers.LoadSwapToList) + ls.DbRs.AddLocalFunc("swap_to_list", appHandlers.LoadSwapToList) ls.DbRs.AddLocalFunc("swap_max_limit", appHandlers.SwapMaxLimit) ls.DbRs.AddLocalFunc("swap_preview", appHandlers.SwapPreview) ls.DbRs.AddLocalFunc("initiate_swap", appHandlers.InitiateSwap) diff --git a/services/registration/change_language.vis b/services/registration/change_language.vis index f20bcfb..dde5c15 100644 --- a/services/registration/change_language.vis +++ b/services/registration/change_language.vis @@ -1,5 +1,5 @@ LOAD reset_account_authorized 0 -LOAD reset_incorrect 0 +LOAD reset_incorrect_pin 0 CATCH incorrect_pin flag_incorrect_pin 1 CATCH pin_entry flag_account_authorized 0 MOUT english 1 diff --git a/services/registration/community_balance.vis b/services/registration/community_balance.vis index a748685..b682ec1 100644 --- a/services/registration/community_balance.vis +++ b/services/registration/community_balance.vis @@ -1,4 +1,4 @@ -LOAD reset_incorrect 6 +LOAD reset_incorrect_pin 6 LOAD fetch_community_balance 0 CATCH api_failure flag_api_call_error 1 MAP fetch_community_balance diff --git a/services/registration/incorrect_pin b/services/registration/incorrect_pin index 13a9562..3bfa96c 100644 --- a/services/registration/incorrect_pin +++ b/services/registration/incorrect_pin @@ -1 +1 @@ -Incorrect PIN. You have: {{.reset_incorrect}} remaining attempt(s). \ No newline at end of file +Incorrect PIN. You have: {{.reset_incorrect_pin}} remaining attempt(s). \ No newline at end of file diff --git a/services/registration/incorrect_pin.vis b/services/registration/incorrect_pin.vis index 74007a7..c9ed985 100644 --- a/services/registration/incorrect_pin.vis +++ b/services/registration/incorrect_pin.vis @@ -1,6 +1,6 @@ -LOAD reset_incorrect 0 -RELOAD reset_incorrect -MAP reset_incorrect +LOAD reset_incorrect_pin 0 +RELOAD reset_incorrect_pin +MAP reset_incorrect_pin CATCH blocked_account flag_account_blocked 1 MOUT retry 1 MOUT quit 9 diff --git a/services/registration/incorrect_pin_swa b/services/registration/incorrect_pin_swa index ed22beb..dce2142 100644 --- a/services/registration/incorrect_pin_swa +++ b/services/registration/incorrect_pin_swa @@ -1 +1 @@ -PIN ulioeka sio sahihi, una majaribio: {{.reset_incorrect}} yaliyobaki \ No newline at end of file +PIN ulioeka sio sahihi, una majaribio: {{.reset_incorrect_pin}} yaliyobaki \ No newline at end of file diff --git a/services/registration/my_balance.vis b/services/registration/my_balance.vis index 166d18f..79d0177 100644 --- a/services/registration/my_balance.vis +++ b/services/registration/my_balance.vis @@ -1,4 +1,4 @@ -LOAD reset_incorrect 6 +LOAD reset_incorrect_pin 6 LOAD check_balance 0 CATCH api_failure flag_api_call_error 1 MAP check_balance diff --git a/services/registration/old_pin.vis b/services/registration/old_pin.vis index ccc2928..38803c8 100644 --- a/services/registration/old_pin.vis +++ b/services/registration/old_pin.vis @@ -1,4 +1,4 @@ -RELOAD reset_incorrect +RELOAD reset_incorrect_pin MOUT back 0 HALT INCMP _ 0 diff --git a/services/registration/pin_management.vis b/services/registration/pin_management.vis index 01bd236..4bc7a8e 100644 --- a/services/registration/pin_management.vis +++ b/services/registration/pin_management.vis @@ -2,7 +2,7 @@ LOAD set_back 6 LOAD authorize_account 16 LOAD reset_allow_update 4 LOAD save_temporary_pin 1 -LOAD reset_incorrect 0 +LOAD reset_incorrect_pin 0 LOAD reset_invalid_pin 6 MOUT change_pin 1 MOUT reset_pin 2 diff --git a/services/registration/swap_initiated.vis b/services/registration/swap_initiated.vis index 69ffc6d..d2018aa 100644 --- a/services/registration/swap_initiated.vis +++ b/services/registration/swap_initiated.vis @@ -1,3 +1,3 @@ -LOAD reset_incorrect 6 +LOAD reset_incorrect_pin 6 LOAD initiate_swap 0 HALT diff --git a/services/registration/transaction_initiated.vis b/services/registration/transaction_initiated.vis index f8cf19c..33ce092 100644 --- a/services/registration/transaction_initiated.vis +++ b/services/registration/transaction_initiated.vis @@ -1,4 +1,4 @@ -LOAD reset_incorrect 6 +LOAD reset_incorrect_pin 6 CATCH incorrect_pin flag_incorrect_pin 1 CATCH _ flag_account_authorized 0 RELOAD get_amount diff --git a/services/registration/view_profile.vis b/services/registration/view_profile.vis index 4f4947c..4b69e03 100644 --- a/services/registration/view_profile.vis +++ b/services/registration/view_profile.vis @@ -1,6 +1,6 @@ LOAD get_profile_info 0 MAP get_profile_info -LOAD reset_incorrect 6 +LOAD reset_incorrect_pin 6 CATCH incorrect_pin flag_incorrect_pin 1 CATCH pin_entry flag_account_authorized 0 MOUT back 0 diff --git a/services/registration/voucher_set.vis b/services/registration/voucher_set.vis index e75c693..0773703 100644 --- a/services/registration/voucher_set.vis +++ b/services/registration/voucher_set.vis @@ -1,4 +1,4 @@ -LOAD reset_incorrect 6 +LOAD reset_incorrect_pin 6 CATCH incorrect_pin flag_incorrect_pin 1 CATCH _ flag_account_authorized 0 LOAD set_voucher 12 From d9fc5440c545db38f812d26bcae8bf0aab2dc785 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 22 May 2025 12:24:10 +0300 Subject: [PATCH 17/25] prevent access to the swap menu if one does not have a voucher --- services/registration/swap_to_list.vis | 1 + 1 file changed, 1 insertion(+) diff --git a/services/registration/swap_to_list.vis b/services/registration/swap_to_list.vis index a2d3bea..47e6577 100644 --- a/services/registration/swap_to_list.vis +++ b/services/registration/swap_to_list.vis @@ -1,3 +1,4 @@ +CATCH no_voucher flag_no_active_voucher 1 LOAD swap_to_list 0 RELOAD swap_to_list MAP swap_to_list From 71496d0015d95c30ce3bf46a2e681f2c25e348cb Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 22 May 2025 12:41:49 +0300 Subject: [PATCH 18/25] change position of the catch statements --- services/registration/swap_initiated.vis | 2 ++ services/registration/swap_preview.vis | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/services/registration/swap_initiated.vis b/services/registration/swap_initiated.vis index d2018aa..eaa7907 100644 --- a/services/registration/swap_initiated.vis +++ b/services/registration/swap_initiated.vis @@ -1,3 +1,5 @@ LOAD reset_incorrect_pin 6 +CATCH incorrect_pin flag_incorrect_pin 1 +CATCH _ flag_account_authorized 0 LOAD initiate_swap 0 HALT diff --git a/services/registration/swap_preview.vis b/services/registration/swap_preview.vis index e7c2b97..7e5276a 100644 --- a/services/registration/swap_preview.vis +++ b/services/registration/swap_preview.vis @@ -7,7 +7,6 @@ LOAD authorize_account 6 HALT RELOAD authorize_account CATCH incorrect_pin flag_incorrect_pin 1 -CATCH . flag_account_authorized 0 INCMP _ 0 INCMP quit 9 INCMP swap_initiated * From dcadea8737490ebf65e179bd260f0a024b71e67c Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 22 May 2025 15:09:46 +0300 Subject: [PATCH 19/25] Convert TokenDecimals (uint8) to string before storing it --- store/swap.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/store/swap.go b/store/swap.go index c3f5a77..b69c061 100644 --- a/store/swap.go +++ b/store/swap.go @@ -175,10 +175,14 @@ func GetSwapToVoucherData(ctx context.Context, store DataStore, sessionId string // UpdateSwapToVoucherData updates the active swap to voucher data in the DataStore. func UpdateSwapToVoucherData(ctx context.Context, store DataStore, sessionId string, data *dataserviceapi.TokenDetails) error { logg.TraceCtxf(ctx, "UpdateSwapToVoucherData", "data", data) + + // Convert TokenDecimals (uint8) to string + tokenDecimalsStr := strconv.FormatUint(uint64(data.TokenDecimals), 10) + // Active swap to voucher data entries activeEntries := map[storedb.DataTyp][]byte{ storedb.DATA_ACTIVE_SWAP_TO_SYM: []byte(data.TokenSymbol), - storedb.DATA_ACTIVE_SWAP_TO_DECIMAL: []byte{data.TokenDecimals}, + storedb.DATA_ACTIVE_SWAP_TO_DECIMAL: []byte(tokenDecimalsStr), storedb.DATA_ACTIVE_SWAP_TO_ADDRESS: []byte(data.TokenAddress), } From 8dc203d111f1bf288fc187cde9132de42ae8860c Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 22 May 2025 15:18:52 +0300 Subject: [PATCH 20/25] use the TemporaryValue to store the user's swap amount input --- handlers/application/menuhandler.go | 8 +++++++- store/swap.go | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/handlers/application/menuhandler.go b/handlers/application/menuhandler.go index 9860d4f..0093279 100644 --- a/handlers/application/menuhandler.go +++ b/handlers/application/menuhandler.go @@ -2940,6 +2940,12 @@ func (h *MenuHandlers) SwapPreview(ctx context.Context, sym string, input []byte 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) @@ -3015,7 +3021,7 @@ func (h *MenuHandlers) InitiateSwap(ctx context.Context, sym string, input []byt res.Content = l.Get( "Your request has been sent. You will receive an SMS when your %s %s has been swapped for %s.", - swapAmountStr, + swapData.TemporaryValue, swapData.ActiveSwapFromSym, swapData.ActiveSwapToSym, ) diff --git a/store/swap.go b/store/swap.go index b69c061..4e2338d 100644 --- a/store/swap.go +++ b/store/swap.go @@ -22,6 +22,7 @@ type SwapData struct { } type SwapPreviewData struct { + TemporaryValue string PublicKey string ActiveSwapMaxAmount string ActiveSwapFromDecimal string @@ -65,6 +66,7 @@ func ReadSwapData(ctx context.Context, store DataStore, sessionId string) (SwapD func ReadSwapPreviewData(ctx context.Context, store DataStore, sessionId string) (SwapPreviewData, error) { data := SwapPreviewData{} fieldToKey := map[string]storedb.DataTyp{ + "TemporaryValue": storedb.DATA_TEMPORARY_VALUE, "PublicKey": storedb.DATA_PUBLIC_KEY, "ActiveSwapMaxAmount": storedb.DATA_ACTIVE_SWAP_MAX_AMOUNT, "ActiveSwapFromDecimal": storedb.DATA_ACTIVE_DECIMAL, From b4bacfdc031ed55ab5c3c49a938b587407bb7a2e Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 22 May 2025 15:34:04 +0300 Subject: [PATCH 21/25] use the updated sarafu-api package with correct PoolSwapURL --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index fffaced..05546e2 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.23.4 require ( git.defalsify.org/vise.git v0.3.2-0.20250507172020-cb22240f1cb9 git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20250417111317-2953f4c2f32e - git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250521141246-6c3719e3b652 + git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250522123108-24224e553de5 git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306 git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694 github.com/alecthomas/assert/v2 v2.2.2 diff --git a/go.sum b/go.sum index 83dc9d8..7019ed2 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,8 @@ git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250521124206- git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250521124206-343d30a2f27c/go.mod h1:R7+mt27mhm4/LCutAHHv87pOsLxXEPO93KXnhP8H6W0= git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250521141246-6c3719e3b652 h1:MmUQRL6Fjq/jlL53h+cUbJ7WwQ+q/yNy/zT05c7bgNg= git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250521141246-6c3719e3b652/go.mod h1:wKHPy1mpOCr9ahkRltwm1yi9qH/3m9xb8hMCX5C0L1o= +git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250522123108-24224e553de5 h1:7gVnkpybzg5lC7C8Rl4dejTbmBVpu5xfMMfda+d498U= +git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250522123108-24224e553de5/go.mod h1:wKHPy1mpOCr9ahkRltwm1yi9qH/3m9xb8hMCX5C0L1o= git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306 h1:Jo+yWysWw/N5BJQtAyEMN8ePVvAyPHv+JG4lQti+1N4= git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306/go.mod h1:FdLwYtzsjOIcDiW4uDgDYnB4Wdzq12uJUe0QHSSPbSo= git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694 h1:DjJlBSz0S13acft5XZDWk7ZYnzElym0xLMYEVgyNJ+E= From df8e0b4d4ad84f2e5578ba05f9cd3a27bb5ce874 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Fri, 23 May 2025 15:28:40 +0300 Subject: [PATCH 22/25] remove misplaced CATCH statement --- services/registration/main.vis | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/services/registration/main.vis b/services/registration/main.vis index 0eb9fc9..c606ff7 100644 --- a/services/registration/main.vis +++ b/services/registration/main.vis @@ -2,10 +2,9 @@ LOAD clear_temporary_value 2 RELOAD clear_temporary_value LOAD manage_vouchers 160 RELOAD manage_vouchers -CATCH api_failure flag_api_call_error 1 +CATCH api_failure flag_api_call_error 1 LOAD check_balance 128 RELOAD check_balance -CATCH api_failure flag_api_call_error 1 MAP check_balance MOUT send 1 MOUT swap 2 From fac703576fd7dddbc8d40a5efbc0a5c4a753c7b1 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Fri, 23 May 2025 15:30:48 +0300 Subject: [PATCH 23/25] updated the go-vise package to commit hash a170e8a79da067e30b3c28a999a4d87fdd07ebaf --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 05546e2..7dc407d 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module git.grassecon.net/grassrootseconomics/sarafu-vise go 1.23.4 require ( - git.defalsify.org/vise.git v0.3.2-0.20250507172020-cb22240f1cb9 + git.defalsify.org/vise.git v0.3.2-0.20250507135825-a170e8a79da0 git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20250417111317-2953f4c2f32e git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250522123108-24224e553de5 git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306 diff --git a/go.sum b/go.sum index 7019ed2..bd39495 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ git.defalsify.org/vise.git v0.3.2-0.20250425131748-8b84f59792ce h1:Uke2jQ4wG97gQKnTzxPyBGyhosrU1IWnRNFHtKVrmrk= git.defalsify.org/vise.git v0.3.2-0.20250425131748-8b84f59792ce/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck= +git.defalsify.org/vise.git v0.3.2-0.20250507135825-a170e8a79da0 h1:ByRD+b39zVOjOzOf3I1z2L6MSiMZCZT8y0P8MMAAorY= +git.defalsify.org/vise.git v0.3.2-0.20250507135825-a170e8a79da0/go.mod h1:flN+Gu+0BX0F6trZc5VpJcO4e6mj/+HUw0Z/UqVTFhk= git.defalsify.org/vise.git v0.3.2-0.20250507172020-cb22240f1cb9 h1:4kjbYw25MHZe9fqSbujPzpFXrYutFfVipvLrcWYnYks= git.defalsify.org/vise.git v0.3.2-0.20250507172020-cb22240f1cb9/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck= git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20250417111317-2953f4c2f32e h1:DcC9qkNl9ny3hxQmsMK6W81+5R/j4ZwYUbvewMI/rlc= From 7228b818e33da44669d957632eb05ce8a0244150 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Tue, 3 Jun 2025 09:19:13 +0200 Subject: [PATCH 24/25] swap-fix (#78) - Resolves a "key not found error" on the swap max limit node - Resolves double PIN requests when authorizing transactions https://git.grassecon.net/grassrootseconomics/sarafu-vise/issues/67 Reviewed-on: https://git.grassecon.net/grassrootseconomics/sarafu-vise/pulls/78 Co-authored-by: alfred-mk Co-committed-by: alfred-mk --- handlers/application/menuhandler.go | 74 ++++++++----------- handlers/application/menuhandler_test.go | 11 ++- services/registration/swap_initiated.vis | 1 - services/registration/swap_limit.vis | 2 - services/registration/swap_to_list.vis | 1 + .../registration/transaction_initiated.vis | 1 - 6 files changed, 35 insertions(+), 55 deletions(-) diff --git a/handlers/application/menuhandler.go b/handlers/application/menuhandler.go index 0093279..86f70b7 100644 --- a/handlers/application/menuhandler.go +++ b/handlers/application/menuhandler.go @@ -630,21 +630,11 @@ func (h *MenuHandlers) incrementIncorrectPINAttempts(ctx context.Context, sessio // resetIncorrectPINAttempts resets the number of incorrect PIN attempts after a correct PIN entry func (h *MenuHandlers) resetIncorrectPINAttempts(ctx context.Context, sessionId string) error { store := h.userdataStore - currentWrongPinAttempts, err := store.ReadEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS) + err := store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(string("0"))) if err != nil { - if db.IsNotFound(err) { - return nil - } + logg.ErrorCtxf(ctx, "failed to reset incorrect PIN attempts ", "key", storedb.DATA_INCORRECT_PIN_ATTEMPTS, "error", err) return err } - currentWrongPinAttemptsCount, _ := strconv.ParseUint(string(currentWrongPinAttempts), 0, 64) - if currentWrongPinAttemptsCount <= uint64(pin.AllowedPINAttempts) { - err = store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(string("0"))) - if err != nil { - logg.ErrorCtxf(ctx, "failed to reset incorrect PIN attempts ", "key", storedb.DATA_INCORRECT_PIN_ATTEMPTS, "value", pin.AllowedPINAttempts, "error", err) - return err - } - } return nil } @@ -1371,7 +1361,13 @@ func (h *MenuHandlers) Authorize(ctx context.Context, sym string, input []byte) flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin") flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized") flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") - flag_invalid_pin, _ := h.flagManager.GetFlag("flag_invalid_pin") + + pinInput := string(input) + + if !pin.IsValidPIN(pinInput) { + res.FlagReset = append(res.FlagReset, flag_account_authorized, flag_allow_update) + return res, nil + } store := h.userdataStore AccountPin, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN) @@ -1379,40 +1375,28 @@ func (h *MenuHandlers) Authorize(ctx context.Context, sym string, input []byte) logg.ErrorCtxf(ctx, "failed to read AccountPin entry with", "key", storedb.DATA_ACCOUNT_PIN, "error", err) return res, err } - str := string(input) - _, err = strconv.Atoi(str) - if len(input) == 4 && err == nil { - if pin.VerifyPIN(string(AccountPin), string(input)) { - if h.st.MatchFlag(flag_account_authorized, false) { - res.FlagReset = append(res.FlagReset, flag_incorrect_pin) - res.FlagSet = append(res.FlagSet, flag_allow_update, flag_account_authorized) - err := h.resetIncorrectPINAttempts(ctx, sessionId) - if err != nil { - return res, err - } - } else { - res.FlagSet = append(res.FlagSet, flag_allow_update) - res.FlagReset = append(res.FlagReset, flag_account_authorized) - err := h.resetIncorrectPINAttempts(ctx, sessionId) - if err != nil { - return res, err - } - } - } else { - err = h.incrementIncorrectPINAttempts(ctx, sessionId) - if err != nil { - return res, err - } - res.FlagSet = append(res.FlagSet, flag_incorrect_pin) - res.FlagReset = append(res.FlagReset, flag_account_authorized) - return res, nil + + // verify that the user provided the correct PIN + if pin.VerifyPIN(string(AccountPin), pinInput) { + // set the required flags for a valid PIN + res.FlagSet = append(res.FlagSet, flag_allow_update, flag_account_authorized) + res.FlagReset = append(res.FlagReset, flag_incorrect_pin) + + err := h.resetIncorrectPINAttempts(ctx, sessionId) + if err != nil { + return res, err } } else { - if string(input) != "0" { - res.FlagSet = append(res.FlagSet, flag_invalid_pin) + // set the required flags for an incorrect PIN + res.FlagSet = append(res.FlagSet, flag_incorrect_pin) + res.FlagReset = append(res.FlagReset, flag_account_authorized, flag_allow_update) + + err = h.incrementIncorrectPINAttempts(ctx, sessionId) + if err != nil { + return res, err } - return res, nil } + return res, nil } @@ -2789,6 +2773,7 @@ func (h *MenuHandlers) LoadSwapToList(ctx context.Context, sym string, input []b // 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, } @@ -2818,8 +2803,7 @@ func (h *MenuHandlers) SwapMaxLimit(ctx context.Context, sym string, input []byt 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) - res.FlagReset = append(res.FlagSet, flag_low_swap_amount) + res.FlagReset = append(res.FlagReset, flag_incorrect_voucher, flag_low_swap_amount) inputStr := string(input) if inputStr == "0" { diff --git a/handlers/application/menuhandler_test.go b/handlers/application/menuhandler_test.go index d07e0cc..a608b7d 100644 --- a/handlers/application/menuhandler_test.go +++ b/handlers/application/menuhandler_test.go @@ -1116,7 +1116,6 @@ func TestAuthorize(t *testing.T) { flag_incorrect_pin, _ := fm.GetFlag("flag_incorrect_pin") flag_account_authorized, _ := fm.GetFlag("flag_account_authorized") flag_allow_update, _ := fm.GetFlag("flag_allow_update") - flag_invalid_pin, _ := fm.GetFlag("flag_invalid_pin") // Set 1234 is the correct account pin accountPIN := "1234" @@ -1134,7 +1133,7 @@ func TestAuthorize(t *testing.T) { expectedResult resource.Result }{ { - name: "Test with correct pin", + name: "Test with correct PIN", input: []byte("1234"), expectedResult: resource.Result{ FlagReset: []uint32{flag_incorrect_pin}, @@ -1142,18 +1141,18 @@ func TestAuthorize(t *testing.T) { }, }, { - name: "Test with incorrect pin", + name: "Test with incorrect PIN", input: []byte("1235"), expectedResult: resource.Result{ - FlagReset: []uint32{flag_account_authorized}, + FlagReset: []uint32{flag_account_authorized, flag_allow_update}, FlagSet: []uint32{flag_incorrect_pin}, }, }, { - name: "Test with pin that is not a 4 digit", + name: "Test with PIN that is not a 4 digit", input: []byte("1235aqds"), expectedResult: resource.Result{ - FlagSet: []uint32{flag_invalid_pin}, + FlagReset: []uint32{flag_account_authorized, flag_allow_update}, }, }, } diff --git a/services/registration/swap_initiated.vis b/services/registration/swap_initiated.vis index eaa7907..4faa7ba 100644 --- a/services/registration/swap_initiated.vis +++ b/services/registration/swap_initiated.vis @@ -1,5 +1,4 @@ LOAD reset_incorrect_pin 6 -CATCH incorrect_pin flag_incorrect_pin 1 CATCH _ flag_account_authorized 0 LOAD initiate_swap 0 HALT diff --git a/services/registration/swap_limit.vis b/services/registration/swap_limit.vis index a9fa737..b0ffb9b 100644 --- a/services/registration/swap_limit.vis +++ b/services/registration/swap_limit.vis @@ -1,5 +1,3 @@ -RELOAD swap_max_limit -CATCH api_failure flag_api_call_error 1 MAP swap_max_limit MOUT back 0 HALT diff --git a/services/registration/swap_to_list.vis b/services/registration/swap_to_list.vis index 47e6577..e966fee 100644 --- a/services/registration/swap_to_list.vis +++ b/services/registration/swap_to_list.vis @@ -7,6 +7,7 @@ MOUT back 0 HALT LOAD swap_max_limit 64 RELOAD swap_max_limit +CATCH api_failure flag_api_call_error 1 CATCH . flag_incorrect_voucher 1 CATCH low_swap_amount flag_low_swap_amount 1 INCMP _ 0 diff --git a/services/registration/transaction_initiated.vis b/services/registration/transaction_initiated.vis index 33ce092..bc524ce 100644 --- a/services/registration/transaction_initiated.vis +++ b/services/registration/transaction_initiated.vis @@ -1,5 +1,4 @@ LOAD reset_incorrect_pin 6 -CATCH incorrect_pin flag_incorrect_pin 1 CATCH _ flag_account_authorized 0 RELOAD get_amount MAP get_amount From 42dadb5eea68d536ce243ce162ca844ecde125af Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Mon, 9 Jun 2025 13:38:19 +0200 Subject: [PATCH 25/25] add-pool-selection (#79) - Add pool selection under "Select vouchers" node - Use the set pool contract address in the swap, or fall back to the default Reviewed-on: https://git.grassecon.net/grassrootseconomics/sarafu-vise/pulls/79 Co-authored-by: alfred-mk Co-committed-by: alfred-mk --- go.mod | 2 +- go.sum | 4 + handlers/application/menuhandler.go | 136 +++++++++++++++++--- handlers/local.go | 4 + services/registration/locale/swa/default.po | 5 +- services/registration/my_vouchers.vis | 3 + services/registration/pool_set | 1 + services/registration/pool_set.vis | 10 ++ services/registration/pool_set_swa | 1 + services/registration/select_pool | 3 + services/registration/select_pool.vis | 20 +++ services/registration/select_pool_menu | 1 + services/registration/select_pool_menu_swa | 1 + services/registration/select_pool_swa | 3 + services/registration/view_pool | 2 + services/registration/view_pool.vis | 10 ++ services/registration/view_pool_swa | 2 + services/registration/voucher_set.vis | 1 - store/db/db.go | 4 + store/pools.go | 49 +++++++ 20 files changed, 244 insertions(+), 18 deletions(-) create mode 100644 services/registration/pool_set create mode 100644 services/registration/pool_set.vis create mode 100644 services/registration/pool_set_swa create mode 100644 services/registration/select_pool create mode 100644 services/registration/select_pool.vis create mode 100644 services/registration/select_pool_menu create mode 100644 services/registration/select_pool_menu_swa create mode 100644 services/registration/select_pool_swa create mode 100644 services/registration/view_pool create mode 100644 services/registration/view_pool.vis create mode 100644 services/registration/view_pool_swa diff --git a/go.mod b/go.mod index 7dc407d..3d137e7 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.23.4 require ( git.defalsify.org/vise.git v0.3.2-0.20250507135825-a170e8a79da0 git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20250417111317-2953f4c2f32e - git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250522123108-24224e553de5 + git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250606194235-b5ccaea57560 git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306 git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694 github.com/alecthomas/assert/v2 v2.2.2 diff --git a/go.sum b/go.sum index bd39495..2dd77f1 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,10 @@ git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250521141246- git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250521141246-6c3719e3b652/go.mod h1:wKHPy1mpOCr9ahkRltwm1yi9qH/3m9xb8hMCX5C0L1o= git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250522123108-24224e553de5 h1:7gVnkpybzg5lC7C8Rl4dejTbmBVpu5xfMMfda+d498U= git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250522123108-24224e553de5/go.mod h1:wKHPy1mpOCr9ahkRltwm1yi9qH/3m9xb8hMCX5C0L1o= +git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250605174108-bf830e92dea2 h1:8cxGb7lSoNGJxjauIRGskp//EqisuOOZPoPj4oDwswE= +git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250605174108-bf830e92dea2/go.mod h1:wKHPy1mpOCr9ahkRltwm1yi9qH/3m9xb8hMCX5C0L1o= +git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250606194235-b5ccaea57560 h1:14QVGEgLdl1LyVS/mJFDVRGXHsH5IgmloNPKfk26K6M= +git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250606194235-b5ccaea57560/go.mod h1:wKHPy1mpOCr9ahkRltwm1yi9qH/3m9xb8hMCX5C0L1o= git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306 h1:Jo+yWysWw/N5BJQtAyEMN8ePVvAyPHv+JG4lQti+1N4= git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306/go.mod h1:FdLwYtzsjOIcDiW4uDgDYnB4Wdzq12uJUe0QHSSPbSo= git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694 h1:DjJlBSz0S13acft5XZDWk7ZYnzElym0xLMYEVgyNJ+E= diff --git a/handlers/application/menuhandler.go b/handlers/application/menuhandler.go index 86f70b7..ebe4308 100644 --- a/handlers/application/menuhandler.go +++ b/handlers/application/menuhandler.go @@ -2245,6 +2245,110 @@ func (h *MenuHandlers) GetVoucherDetails(ctx context.Context, sym string, input return res, nil } +// GetDefaultPool returns the current user's Pool. If none is set, it returns the default config pool. +func (h *MenuHandlers) GetDefaultPool(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 + activePoolSym, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_SYM) + if err != nil { + if db.IsNotFound(err) { + // set the default as the response + res.Content = config.DefaultPoolSymbol() + return res, nil + } + + logg.ErrorCtxf(ctx, "failed to read the activePoolSym entry with", "key", storedb.DATA_ACTIVE_POOL_SYM, "error", err) + return res, err + } + + res.Content = string(activePoolSym) + + return res, nil +} + +// ViewPool retrieves the pool details from the user store +// and displays it to the user for them to select it. +func (h *MenuHandlers) ViewPool(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_incorrect_pool, _ := h.flagManager.GetFlag("flag_incorrect_pool") + + inputStr := string(input) + + poolData, err := store.GetPoolData(ctx, h.userdataStore, sessionId, inputStr) + if err != nil { + return res, fmt.Errorf("failed to retrieve pool data: %v", err) + } + + if poolData == nil { + flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") + + // no match found. Call the API using the inputStr as the symbol + poolResp, err := h.accountService.RetrievePoolDetails(ctx, inputStr) + if err != nil { + res.FlagSet = append(res.FlagSet, flag_api_error) + return res, nil + } + + if len(poolResp.PoolSymbol) == 0 { + // If the API does not return the data, set the flag + res.FlagSet = append(res.FlagSet, flag_incorrect_pool) + return res, nil + } + + poolData = poolResp + } + + if err := store.StoreTemporaryPool(ctx, h.userdataStore, sessionId, poolData); err != nil { + logg.ErrorCtxf(ctx, "failed on StoreTemporaryPool", "error", err) + return res, err + } + + res.FlagReset = append(res.FlagReset, flag_incorrect_pool) + res.Content = l.Get("Name: %s\nSymbol: %s", poolData.PoolName, poolData.PoolSymbol) + + return res, nil +} + +// SetPool retrieves the temp pool data and sets it as the active data. +func (h *MenuHandlers) SetPool(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") + } + + // Get temporary data + tempData, err := store.GetTemporaryPoolData(ctx, h.userdataStore, sessionId) + if err != nil { + logg.ErrorCtxf(ctx, "failed on GetTemporaryPoolData", "error", err) + return res, err + } + + // Set as active and clear temporary data + if err := store.UpdatePoolData(ctx, h.userdataStore, sessionId, tempData); err != nil { + logg.ErrorCtxf(ctx, "failed on UpdatePoolData", "error", err) + return res, err + } + + res.Content = tempData.PoolSymbol + return res, nil +} + // CheckTransactions retrieves the transactions from the API using the "PublicKey" and stores to prefixDb. func (h *MenuHandlers) CheckTransactions(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result @@ -2716,25 +2820,27 @@ func (h *MenuHandlers) LoadSwapToList(ctx context.Context, sym string, input []b return res, nil } - defaultPool := dataserviceapi.PoolDetails{ - PoolName: config.DefaultPoolName(), - PoolSymbol: config.DefaultPoolSymbol(), - PoolContractAdrress: config.DefaultPoolAddress(), - LimiterAddress: "", - VoucherRegistry: "", - } - - activePoolAddress := defaultPool.PoolContractAdrress - - // set the active pool contract address - err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_ADDRESS, []byte(activePoolAddress)) + // Get active pool address or fall back to default + var activePoolAddress []byte + activePoolAddress, err = userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_ADDRESS) if err != nil { - logg.ErrorCtxf(ctx, "failed to write active PoolContractAdrress entry with", "key", storedb.DATA_ACTIVE_POOL_ADDRESS, "value", activePoolAddress, "error", err) - return res, err + 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 + } } // call the api using the ActivePoolAddress and ActiveVoucherAddress to check if it is part of the pool - r, err := h.accountService.CheckTokenInPool(ctx, activePoolAddress, string(activeAddress)) + 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) diff --git a/handlers/local.go b/handlers/local.go index 01c8800..4ff1e13 100644 --- a/handlers/local.go +++ b/handlers/local.go @@ -112,6 +112,10 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountService) ls.DbRs.AddLocalFunc("view_voucher", appHandlers.ViewVoucher) ls.DbRs.AddLocalFunc("set_voucher", appHandlers.SetVoucher) ls.DbRs.AddLocalFunc("get_voucher_details", appHandlers.GetVoucherDetails) + ls.DbRs.AddLocalFunc("get_default_pool", appHandlers.GetDefaultPool) + ls.DbRs.AddLocalFunc("get_pools", appHandlers.GetPools) + ls.DbRs.AddLocalFunc("view_pool", appHandlers.ViewPool) + ls.DbRs.AddLocalFunc("set_pool", appHandlers.SetPool) ls.DbRs.AddLocalFunc("validate_blocked_number", appHandlers.ValidateBlockedNumber) ls.DbRs.AddLocalFunc("retrieve_blocked_number", appHandlers.RetrieveBlockedNumber) ls.DbRs.AddLocalFunc("reset_unregistered_number", appHandlers.ResetUnregisteredNumber) diff --git a/services/registration/locale/swa/default.po b/services/registration/locale/swa/default.po index fbd92fb..235624a 100644 --- a/services/registration/locale/swa/default.po +++ b/services/registration/locale/swa/default.po @@ -38,4 +38,7 @@ msgid "%s balance: %s\n" msgstr "%s salio: %s\n" msgid "%s is not in %s. Please update your voucher and try again." -msgstr "%s haipo kwenye %s. Tafadhali badilisha sarafu yako na ujaribu tena." \ No newline at end of file +msgstr "%s haipo kwenye %s. Tafadhali badilisha sarafu yako na ujaribu tena." + +msgid "Name: %s\nSymbol: %s" +msgstr "Jina: %s\nSarafu: %s" \ No newline at end of file diff --git a/services/registration/my_vouchers.vis b/services/registration/my_vouchers.vis index e79438e..d196e07 100644 --- a/services/registration/my_vouchers.vis +++ b/services/registration/my_vouchers.vis @@ -2,8 +2,11 @@ LOAD reset_account_authorized 16 RELOAD reset_account_authorized MOUT select_voucher 1 MOUT voucher_details 2 +MOUT select_pool 3 MOUT back 0 HALT INCMP _ 0 INCMP select_voucher 1 INCMP voucher_details 2 +INCMP select_pool 3 +INCMP . * diff --git a/services/registration/pool_set b/services/registration/pool_set new file mode 100644 index 0000000..b5ec2f3 --- /dev/null +++ b/services/registration/pool_set @@ -0,0 +1 @@ +Success! {{.set_pool}} is now your active pool. \ No newline at end of file diff --git a/services/registration/pool_set.vis b/services/registration/pool_set.vis new file mode 100644 index 0000000..5c7b532 --- /dev/null +++ b/services/registration/pool_set.vis @@ -0,0 +1,10 @@ +LOAD reset_incorrect_pin 6 +CATCH _ flag_account_authorized 0 +LOAD set_pool 20 +MAP set_pool +MOUT back 0 +MOUT quit 9 +HALT +INCMP ^ 0 +INCMP quit 9 +INCMP ^ * diff --git a/services/registration/pool_set_swa b/services/registration/pool_set_swa new file mode 100644 index 0000000..3c527f6 --- /dev/null +++ b/services/registration/pool_set_swa @@ -0,0 +1 @@ +Hongera! {{.set_pool}} ni bwawa la Sarafu linalotumika sasa. \ No newline at end of file diff --git a/services/registration/select_pool b/services/registration/select_pool new file mode 100644 index 0000000..4daad6a --- /dev/null +++ b/services/registration/select_pool @@ -0,0 +1,3 @@ +Enter number or symbol to set the default pool: +Current: {{.get_default_pool}} +{{.get_pools}} \ No newline at end of file diff --git a/services/registration/select_pool.vis b/services/registration/select_pool.vis new file mode 100644 index 0000000..4436097 --- /dev/null +++ b/services/registration/select_pool.vis @@ -0,0 +1,20 @@ +CATCH no_voucher flag_no_active_voucher 1 +LOAD get_pools 0 +MAP get_pools +LOAD get_default_pool 20 +RELOAD get_default_pool +MAP get_default_pool +MOUT back 0 +MOUT quit 99 +MNEXT next 88 +MPREV prev 98 +HALT +INCMP > 88 +INCMP < 98 +INCMP _ 0 +INCMP quit 99 +LOAD view_pool 80 +RELOAD view_pool +CATCH api_failure flag_api_call_error 1 +CATCH . flag_incorrect_pool 1 +INCMP view_pool * diff --git a/services/registration/select_pool_menu b/services/registration/select_pool_menu new file mode 100644 index 0000000..5fc7cb4 --- /dev/null +++ b/services/registration/select_pool_menu @@ -0,0 +1 @@ +Select pool \ No newline at end of file diff --git a/services/registration/select_pool_menu_swa b/services/registration/select_pool_menu_swa new file mode 100644 index 0000000..546dc7d --- /dev/null +++ b/services/registration/select_pool_menu_swa @@ -0,0 +1 @@ +Chagua Bwawa \ No newline at end of file diff --git a/services/registration/select_pool_swa b/services/registration/select_pool_swa new file mode 100644 index 0000000..5efcc91 --- /dev/null +++ b/services/registration/select_pool_swa @@ -0,0 +1,3 @@ +Chagua nambari au ishara kuweka bwawa la sarafu: +La sasa: {{.get_default_pool}} +{{.get_pools}} \ No newline at end of file diff --git a/services/registration/view_pool b/services/registration/view_pool new file mode 100644 index 0000000..11109ea --- /dev/null +++ b/services/registration/view_pool @@ -0,0 +1,2 @@ +Enter PIN to confirm selection: +{{.view_pool}} \ No newline at end of file diff --git a/services/registration/view_pool.vis b/services/registration/view_pool.vis new file mode 100644 index 0000000..f506590 --- /dev/null +++ b/services/registration/view_pool.vis @@ -0,0 +1,10 @@ +MAP view_pool +MOUT back 0 +MOUT quit 9 +LOAD authorize_account 6 +HALT +RELOAD authorize_account +CATCH incorrect_pin flag_incorrect_pin 1 +INCMP _ 0 +INCMP quit 9 +INCMP pool_set * diff --git a/services/registration/view_pool_swa b/services/registration/view_pool_swa new file mode 100644 index 0000000..80c6a00 --- /dev/null +++ b/services/registration/view_pool_swa @@ -0,0 +1,2 @@ +Weka PIN ili kuthibitisha chaguo: +{{.view_pool}} \ No newline at end of file diff --git a/services/registration/voucher_set.vis b/services/registration/voucher_set.vis index 0773703..637bafd 100644 --- a/services/registration/voucher_set.vis +++ b/services/registration/voucher_set.vis @@ -1,5 +1,4 @@ LOAD reset_incorrect_pin 6 -CATCH incorrect_pin flag_incorrect_pin 1 CATCH _ flag_account_authorized 0 LOAD set_voucher 12 MAP set_voucher diff --git a/store/db/db.go b/store/db/db.go index e6e1eef..5ada4f6 100644 --- a/store/db/db.go +++ b/store/db/db.go @@ -85,6 +85,10 @@ const ( DATA_ACTIVE_SWAP_MAX_AMOUNT // Holds the active swap amount for the swap DATA_ACTIVE_SWAP_AMOUNT + // Holds the active pool name for the swap + DATA_ACTIVE_POOL_NAME + // Holds the active pool symbol for the swap + DATA_ACTIVE_POOL_SYM ) const ( diff --git a/store/pools.go b/store/pools.go index 1e01329..0cf918d 100644 --- a/store/pools.go +++ b/store/pools.go @@ -91,3 +91,52 @@ func MatchPool(input, names, symbols, addresses string) (name, symbol, address s } return } + +// StoreTemporaryPool saves pool metadata as temporary entries in the DataStore. +func StoreTemporaryPool(ctx context.Context, store DataStore, sessionId string, data *dataserviceapi.PoolDetails) error { + tempData := fmt.Sprintf("%s,%s,%s", data.PoolName, data.PoolSymbol, data.PoolContractAdrress) + + if err := store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(tempData)); err != nil { + return err + } + + return nil +} + +// GetTemporaryPoolData retrieves temporary pool metadata from the DataStore. +func GetTemporaryPoolData(ctx context.Context, store DataStore, sessionId string) (*dataserviceapi.PoolDetails, error) { + temp_data, err := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) + if err != nil { + return nil, err + } + + values := strings.SplitN(string(temp_data), ",", 3) + + data := &dataserviceapi.PoolDetails{} + + data.PoolName = values[0] + data.PoolSymbol = values[1] + data.PoolContractAdrress = values[2] + + return data, nil +} + +// UpdatePoolData updates the active pool data in the DataStore. +func UpdatePoolData(ctx context.Context, store DataStore, sessionId string, data *dataserviceapi.PoolDetails) error { + logg.TraceCtxf(ctx, "dtal", "data", data) + // Active pool data entry + activeEntries := map[storedb.DataTyp][]byte{ + storedb.DATA_ACTIVE_POOL_NAME: []byte(data.PoolName), + storedb.DATA_ACTIVE_POOL_SYM: []byte(data.PoolSymbol), + storedb.DATA_ACTIVE_POOL_ADDRESS: []byte(data.PoolContractAdrress), + } + + // Write active data + for key, value := range activeEntries { + if err := store.WriteEntry(ctx, sessionId, key, value); err != nil { + return err + } + } + + return nil +} \ No newline at end of file