From 4d687cac2e30511d265f71c8fdb252cc57b07304 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Wed, 30 Jul 2025 11:46:22 +0300 Subject: [PATCH 01/44] updated the MaxAmount description comment --- handlers/application/send.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/application/send.go b/handlers/application/send.go index 3580288..dad269e 100644 --- a/handlers/application/send.go +++ b/handlers/application/send.go @@ -189,7 +189,7 @@ func (h *MenuHandlers) ResetTransactionAmount(ctx context.Context, sym string, i return res, nil } -// MaxAmount gets the current balance from the API and sets it as +// MaxAmount gets the current sender's balance from the store and sets it as // the result content. func (h *MenuHandlers) MaxAmount(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result From f441b3b2aff767065352a7f40b3bafccbef0f60e Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Mon, 4 Aug 2025 11:18:45 +0300 Subject: [PATCH 02/44] split the ValidateRecipient and check the transaction type --- handlers/application/send.go | 243 +++++++++++++++++++++-------------- 1 file changed, 146 insertions(+), 97 deletions(-) diff --git a/handlers/application/send.go b/handlers/application/send.go index dad269e..6170413 100644 --- a/handlers/application/send.go +++ b/handlers/application/send.go @@ -10,7 +10,6 @@ import ( "git.defalsify.org/vise.git/resource" "git.grassecon.net/grassrootseconomics/common/identity" "git.grassecon.net/grassrootseconomics/common/phone" - "git.grassecon.net/grassrootseconomics/sarafu-api/models" "git.grassecon.net/grassrootseconomics/sarafu-vise/config" "git.grassecon.net/grassrootseconomics/sarafu-vise/store" storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" @@ -19,123 +18,168 @@ import ( ) // ValidateRecipient validates that the given input is valid. -// -// TODO: split up functino func (h *MenuHandlers) ValidateRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result - var AliasAddressResult string - var AliasAddress *models.AliasAddress store := h.userdataStore + flag_invalid_recipient, _ := h.flagManager.GetFlag("flag_invalid_recipient") sessionId, ok := ctx.Value("SessionId").(string) if !ok { return res, fmt.Errorf("missing session") } - flag_invalid_recipient, _ := h.flagManager.GetFlag("flag_invalid_recipient") - flag_invalid_recipient_with_invite, _ := h.flagManager.GetFlag("flag_invalid_recipient_with_invite") - flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") // remove white spaces recipient := strings.ReplaceAll(string(input), " ", "") + if recipient == "0" { + return res, nil + } - if recipient != "0" { - recipientType, err := identity.CheckRecipient(recipient) - if err != nil { - // Invalid recipient format (not a phone number, address, or valid alias format) - res.FlagSet = append(res.FlagSet, flag_invalid_recipient) + recipientType, err := identity.CheckRecipient(recipient) + if err != nil { + // Invalid recipient format (not a phone number, address, or valid alias format) + res.FlagSet = append(res.FlagSet, flag_invalid_recipient) + res.Content = recipient + + return res, nil + } + + // save the recipient as the temporaryRecipient + err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(recipient)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write temporaryRecipient entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", recipient, "error", err) + return res, err + } + + switch recipientType { + case "phone number": + return h.handlePhoneNumber(ctx, sessionId, recipient, &res) + case "address": + return h.handleAddress(ctx, sessionId, recipient, &res) + case "alias": + return h.handleAlias(ctx, sessionId, recipient, &res) + } + + return res, nil +} + +func (h *MenuHandlers) handlePhoneNumber(ctx context.Context, sessionId, recipient string, res *resource.Result) (resource.Result, error) { + store := h.userdataStore + flag_invalid_recipient_with_invite, _ := h.flagManager.GetFlag("flag_invalid_recipient_with_invite") + + formattedNumber, err := phone.FormatPhoneNumber(recipient) + if err != nil { + logg.ErrorCtxf(ctx, "Failed to format phone number", "recipient", recipient, "error", err) + return *res, err + } + + publicKey, err := store.ReadEntry(ctx, formattedNumber, storedb.DATA_PUBLIC_KEY) + if err != nil { + if db.IsNotFound(err) { + logg.InfoCtxf(ctx, "Unregistered phone number", "recipient", recipient) + res.FlagSet = append(res.FlagSet, flag_invalid_recipient_with_invite) res.Content = recipient - - return res, nil + return *res, nil } + logg.ErrorCtxf(ctx, "Failed to read publicKey", "error", err) + return *res, err + } - // save the recipient as the temporaryRecipient - err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(recipient)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write temporaryRecipient entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", recipient, "error", err) - return res, err + if err := store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, publicKey); err != nil { + logg.ErrorCtxf(ctx, "Failed to write recipient", "value", string(publicKey), "error", err) + return *res, err + } + + senderSym, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM) + if err != nil { + logg.ErrorCtxf(ctx, "Failed to read sender activeSym", "error", err) + return *res, err + } + recipientActiveToken, err := store.ReadEntry(ctx, formattedNumber, storedb.DATA_ACTIVE_SYM) + if err != nil { + logg.ErrorCtxf(ctx, "Failed to read recipient activeSym", "error", err) + return *res, err + } + + txType := "swap" + if senderSym != nil && recipientActiveToken != nil && string(senderSym) == string(recipientActiveToken) { + txType = "normal" + } + if err := store.WriteEntry(ctx, sessionId, storedb.DATA_SEND_TRANSACTION_TYPE, []byte(txType)); err != nil { + logg.ErrorCtxf(ctx, "Failed to write tx type", "type", txType, "error", err) + return *res, err + } + if err := store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT_ACTIVE_TOKEN, recipientActiveToken); err != nil { + logg.ErrorCtxf(ctx, "Failed to write recipient active token", "error", err) + return *res, err + } + + return *res, nil +} + +func (h *MenuHandlers) handleAddress(ctx context.Context, sessionId, recipient string, res *resource.Result) (resource.Result, error) { + store := h.userdataStore + + address := ethutils.ChecksumAddress(recipient) + if err := store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, []byte(address)); err != nil { + logg.ErrorCtxf(ctx, "Failed to write recipient address", "error", err) + return *res, err + } + if err := store.WriteEntry(ctx, sessionId, storedb.DATA_SEND_TRANSACTION_TYPE, []byte("normal")); err != nil { + logg.ErrorCtxf(ctx, "Failed to write tx type for address", "error", err) + return *res, err + } + + return *res, nil +} + +func (h *MenuHandlers) handleAlias(ctx context.Context, sessionId, recipient string, res *resource.Result) (resource.Result, error) { + store := h.userdataStore + flag_invalid_recipient, _ := h.flagManager.GetFlag("flag_invalid_recipient") + flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") + + var AliasAddressResult string + + if strings.Contains(recipient, ".") { + alias, err := h.accountService.CheckAliasAddress(ctx, recipient) + if err == nil { + AliasAddressResult = alias.Address + } else { + logg.ErrorCtxf(ctx, "Failed to resolve alias", "alias", recipient, "error", err) } + } else { + for _, domain := range config.SearchDomains() { + fqdn := fmt.Sprintf("%s.%s", recipient, domain) + logg.InfoCtxf(ctx, "Trying alias", "fqdn", fqdn) - switch recipientType { - case "phone number": - // format the phone number - formattedNumber, err := phone.FormatPhoneNumber(recipient) - if err != nil { - logg.ErrorCtxf(ctx, "Failed to format the phone number: %s", recipient, "error", err) - return res, err - } - - // Check if the phone number is registered - publicKey, err := store.ReadEntry(ctx, formattedNumber, storedb.DATA_PUBLIC_KEY) - if err != nil { - if db.IsNotFound(err) { - logg.InfoCtxf(ctx, "Unregistered phone number: %s", recipient) - res.FlagSet = append(res.FlagSet, flag_invalid_recipient_with_invite) - res.Content = recipient - return res, nil - } - - logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err) - return res, err - } - - // Save the publicKey as the recipient - err = store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, publicKey) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write recipient entry with", "key", storedb.DATA_RECIPIENT, "value", string(publicKey), "error", err) - return res, err - } - - case "address": - // checksum the address - address := ethutils.ChecksumAddress(recipient) - - // Save the valid Ethereum address as the recipient - err = store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, []byte(address)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write recipient entry with", "key", storedb.DATA_RECIPIENT, "value", recipient, "error", err) - return res, err - } - - case "alias": - if strings.Contains(recipient, ".") { - AliasAddress, err = h.accountService.CheckAliasAddress(ctx, recipient) - if err == nil { - AliasAddressResult = AliasAddress.Address - } else { - logg.ErrorCtxf(ctx, "failed to resolve alias", "alias", recipient, "error_alias_check", err) - } + alias, err := h.accountService.CheckAliasAddress(ctx, fqdn) + if err == nil { + res.FlagReset = append(res.FlagReset, flag_api_error) + AliasAddressResult = alias.Address + break } else { - //Perform a search for each search domain,break on first match - for _, domain := range config.SearchDomains() { - fqdn := fmt.Sprintf("%s.%s", recipient, domain) - logg.InfoCtxf(ctx, "Resolving with fqdn alias", "alias", fqdn) - AliasAddress, err = h.accountService.CheckAliasAddress(ctx, fqdn) - if err == nil { - res.FlagReset = append(res.FlagReset, flag_api_error) - AliasAddressResult = AliasAddress.Address - continue - } else { - res.FlagSet = append(res.FlagSet, flag_api_error) - logg.ErrorCtxf(ctx, "failed to resolve alias", "alias", recipient, "error_alias_check", err) - return res, nil - } - } - } - if AliasAddressResult == "" { - res.Content = recipient - res.FlagSet = append(res.FlagSet, flag_invalid_recipient) - return res, nil - } else { - err = store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, []byte(AliasAddressResult)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write recipient entry with", "key", storedb.DATA_RECIPIENT, "value", AliasAddressResult, "error", err) - return res, err - } + res.FlagSet = append(res.FlagSet, flag_api_error) + logg.ErrorCtxf(ctx, "Alias resolution failed", "alias", fqdn, "error", err) + return *res, nil } } } - return res, nil + if AliasAddressResult == "" { + res.FlagSet = append(res.FlagSet, flag_invalid_recipient) + res.Content = recipient + return *res, nil + } + + if err := store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, []byte(AliasAddressResult)); err != nil { + logg.ErrorCtxf(ctx, "Failed to store alias recipient", "error", err) + return *res, err + } + if err := store.WriteEntry(ctx, sessionId, storedb.DATA_SEND_TRANSACTION_TYPE, []byte("normal")); err != nil { + logg.ErrorCtxf(ctx, "Failed to write tx type for alias", "error", err) + return *res, err + } + + return *res, nil } // TransactionReset resets the previous transaction data (Recipient and Amount) @@ -162,6 +206,11 @@ func (h *MenuHandlers) TransactionReset(ctx context.Context, sym string, input [ return res, nil } + err = store.WriteEntry(ctx, sessionId, storedb.DATA_SEND_TRANSACTION_TYPE, []byte("")) + if err != nil { + return res, nil + } + res.FlagReset = append(res.FlagReset, flag_invalid_recipient, flag_invalid_recipient_with_invite) return res, nil @@ -283,7 +332,7 @@ func (h *MenuHandlers) GetRecipient(ctx context.Context, sym string, input []byt recipient, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) if len(recipient) == 0 { logg.ErrorCtxf(ctx, "recipient is empty", "key", storedb.DATA_TEMPORARY_VALUE) - return res, fmt.Errorf("Data error encountered") + return res, fmt.Errorf("data error encountered") } res.Content = string(recipient) From 758463ee8c3731547a33ab582d06e5a7e3e20905 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Mon, 4 Aug 2025 11:19:22 +0300 Subject: [PATCH 03/44] add transaction data keys --- store/db/db.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/store/db/db.go b/store/db/db.go index 5ada4f6..4b133bc 100644 --- a/store/db/db.go +++ b/store/db/db.go @@ -89,6 +89,10 @@ const ( DATA_ACTIVE_POOL_NAME // Holds the active pool symbol for the swap DATA_ACTIVE_POOL_SYM + // Holds the send transaction type + DATA_SEND_TRANSACTION_TYPE + // Holds the recipient active token (RAT) + DATA_RECIPIENT_ACTIVE_TOKEN ) const ( From b6de057cc48ec8fccb80eb7fcb0ecc85259a5499 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Mon, 4 Aug 2025 11:21:14 +0300 Subject: [PATCH 04/44] add a reset for the DATA_RECIPIENT_ACTIVE_TOKEN key --- handlers/application/send.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/handlers/application/send.go b/handlers/application/send.go index 6170413..5ccc58d 100644 --- a/handlers/application/send.go +++ b/handlers/application/send.go @@ -211,6 +211,11 @@ func (h *MenuHandlers) TransactionReset(ctx context.Context, sym string, input [ return res, nil } + err = store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT_ACTIVE_TOKEN, []byte("")) + if err != nil { + return res, nil + } + res.FlagReset = append(res.FlagReset, flag_invalid_recipient, flag_invalid_recipient_with_invite) return res, nil From 5b82afa768584fe8fab61a51f64dde40bb3477b4 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Mon, 4 Aug 2025 13:50:21 +0300 Subject: [PATCH 05/44] added the DATA_RECIPIENT_PHONE_NUMBER to store the formatted phone number --- store/db/db.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/store/db/db.go b/store/db/db.go index 4b133bc..c21a645 100644 --- a/store/db/db.go +++ b/store/db/db.go @@ -93,6 +93,8 @@ const ( DATA_SEND_TRANSACTION_TYPE // Holds the recipient active token (RAT) DATA_RECIPIENT_ACTIVE_TOKEN + // Holds the recipient formatted phone number + DATA_RECIPIENT_PHONE_NUMBER ) const ( From e274967c8e767c7c468ddfd2c7a0a5fe919d1392 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Mon, 25 Aug 2025 10:52:22 +0300 Subject: [PATCH 06/44] update the handlePhoneNumber logic to cover new users or those without an active voucher --- handlers/application/send.go | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/handlers/application/send.go b/handlers/application/send.go index 5ccc58d..805539a 100644 --- a/handlers/application/send.go +++ b/handlers/application/send.go @@ -94,23 +94,31 @@ func (h *MenuHandlers) handlePhoneNumber(ctx context.Context, sessionId, recipie logg.ErrorCtxf(ctx, "Failed to read sender activeSym", "error", err) return *res, err } - recipientActiveToken, err := store.ReadEntry(ctx, formattedNumber, storedb.DATA_ACTIVE_SYM) - if err != nil { - logg.ErrorCtxf(ctx, "Failed to read recipient activeSym", "error", err) - return *res, err - } + + recipientActiveToken, _ := store.ReadEntry(ctx, formattedNumber, storedb.DATA_ACTIVE_SYM) txType := "swap" - if senderSym != nil && recipientActiveToken != nil && string(senderSym) == string(recipientActiveToken) { + + // recipient has no active token → normal transaction + if recipientActiveToken == nil { + txType = "normal" + } else if senderSym != nil && string(senderSym) == string(recipientActiveToken) { + // recipient has active token same as sender → normal transaction txType = "normal" } + + // save transaction type if err := store.WriteEntry(ctx, sessionId, storedb.DATA_SEND_TRANSACTION_TYPE, []byte(txType)); err != nil { logg.ErrorCtxf(ctx, "Failed to write tx type", "type", txType, "error", err) return *res, err } - if err := store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT_ACTIVE_TOKEN, recipientActiveToken); err != nil { - logg.ErrorCtxf(ctx, "Failed to write recipient active token", "error", err) - return *res, err + + // only save recipient’s active token if it exists + if recipientActiveToken != nil { + if err := store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT_ACTIVE_TOKEN, recipientActiveToken); err != nil { + logg.ErrorCtxf(ctx, "Failed to write recipient active token", "error", err) + return *res, err + } } return *res, nil From 7e1042c6a9fe775e4a4534dba875272df52191f9 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Mon, 25 Aug 2025 12:46:16 +0300 Subject: [PATCH 07/44] update the MaxAmount logic to check the swap capability for swap transactions --- handlers/application/send.go | 142 +++++++++++++++++++++++++++++++++-- 1 file changed, 136 insertions(+), 6 deletions(-) diff --git a/handlers/application/send.go b/handlers/application/send.go index 805539a..1bdae5d 100644 --- a/handlers/application/send.go +++ b/handlers/application/send.go @@ -251,29 +251,159 @@ func (h *MenuHandlers) ResetTransactionAmount(ctx context.Context, sym string, i return res, nil } -// MaxAmount gets the current sender's balance from the store and sets it as +// MaxAmount checks the transaction type to determine the displayed max amount. +// If the transaction type is "swap", it checks the max swappable amount and sets this as the content. +// If the transaction type is "normal", gets the current sender's balance from the store and sets it as // the result content. func (h *MenuHandlers) MaxAmount(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") } - store := h.userdataStore - activeBal, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_BAL) + flag_api_error, _ := h.flagManager.GetFlag("flag_api_error") + userStore := h.userdataStore + + // Fetch session data + transactionType, activeBal, activeSym, activeAddress, publicKey, activeDecimal, err := h.getSessionData(ctx, sessionId) if err != nil { - logg.ErrorCtxf(ctx, "failed to read activeBal entry with", "key", storedb.DATA_ACTIVE_BAL, "error", err) return res, err } - res.Content = string(activeBal) + // Format the active balance amount to 2 decimal places + formattedBalance, _ := store.TruncateDecimalString(string(activeBal), 2) + // If normal transaction, return balance + if string(transactionType) == "normal" { + res.Content = fmt.Sprintf("%s %s", formattedBalance, string(activeSym)) + return res, nil + } + + // Get recipient token address + recipientTokenAddress, err := h.getRecipientTokenAddress(ctx, sessionId) + if err != nil { + // fallback to normal + res.Content = fmt.Sprintf("%s %s", formattedBalance, string(activeSym)) + return res, nil + } + + // Resolve active pool address + activePoolAddress, err := h.resolveActivePoolAddress(ctx, sessionId) + if err != nil { + return res, err + } + + // Check if sender token is swappable + canSwap, err := h.accountService.CheckTokenInPool(ctx, string(activePoolAddress), string(activeAddress)) + if err != nil || !canSwap.CanSwapFrom { + if err != nil { + res.FlagSet = append(res.FlagSet, flag_api_error) + logg.ErrorCtxf(ctx, "failed on CheckTokenInPool", "error", err) + } + res.Content = fmt.Sprintf("%s %s", formattedBalance, string(activeSym)) + return res, err + } + + // Calculate max swappable amount + maxStr, err := h.calculateSwapMaxAmount(ctx, activePoolAddress, activeAddress, recipientTokenAddress, publicKey, activeDecimal) + if err != nil { + res.FlagSet = append(res.FlagSet, flag_api_error) + return res, err + } + + // Fallback if below minimum + maxFloat, _ := strconv.ParseFloat(maxStr, 64) + if maxFloat < 0.1 { + res.Content = fmt.Sprintf("%s %s", formattedBalance, string(activeSym)) + return res, nil + } + + // Save max swap amount and return + 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", "value", maxStr, "error", err) + return res, err + } + + res.Content = fmt.Sprintf("%s %s", maxStr, string(activeSym)) return res, nil } +func (h *MenuHandlers) getSessionData(ctx context.Context, sessionId string) (transactionType, activeBal, activeSym, activeAddress, publicKey, activeDecimal []byte, err error) { + store := h.userdataStore + + transactionType, err = store.ReadEntry(ctx, sessionId, storedb.DATA_SEND_TRANSACTION_TYPE) + if err != nil { + return + } + activeBal, err = store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_BAL) + if err != nil { + return + } + activeAddress, err = store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS) + if err != nil { + return + } + activeSym, err = store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM) + if err != nil { + return + } + publicKey, err = store.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY) + if err != nil { + return + } + activeDecimal, err = store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_DECIMAL) + return +} + +func (h *MenuHandlers) getRecipientTokenAddress(ctx context.Context, sessionId string) ([]byte, error) { + store := h.userdataStore + recipientPhone, err := store.ReadEntry(ctx, sessionId, storedb.DATA_RECIPIENT_PHONE_NUMBER) + if err != nil { + return nil, err + } + return store.ReadEntry(ctx, string(recipientPhone), storedb.DATA_ACTIVE_ADDRESS) +} + +func (h *MenuHandlers) resolveActivePoolAddress(ctx context.Context, sessionId string) ([]byte, error) { + store := h.userdataStore + addr, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_ADDRESS) + if err == nil { + return addr, nil + } + if db.IsNotFound(err) { + defaultAddr := []byte(config.DefaultPoolAddress()) + if err := store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_ADDRESS, defaultAddr); err != nil { + logg.ErrorCtxf(ctx, "failed to write default pool address", "error", err) + return nil, err + } + return defaultAddr, nil + } + logg.ErrorCtxf(ctx, "failed to read active pool address", "error", err) + return nil, err +} + +func (h *MenuHandlers) calculateSwapMaxAmount(ctx context.Context, poolAddress, fromAddress, toAddress, publicKey, decimal []byte) (string, error) { + swapLimit, err := h.accountService.GetSwapFromTokenMaxLimit( + ctx, + string(poolAddress), + string(fromAddress), + string(toAddress), + string(publicKey), + ) + if err != nil { + logg.ErrorCtxf(ctx, "failed on GetSwapFromTokenMaxLimit", "error", err) + return "", err + } + + scaled := store.ScaleDownBalance(swapLimit.Max, string(decimal)) + + formattedAmount, _ := store.TruncateDecimalString(string(scaled), 2) + return formattedAmount, nil +} + // ValidateAmount ensures that the given input is a valid amount and that // it is not more than the current balance. func (h *MenuHandlers) ValidateAmount(ctx context.Context, sym string, input []byte) (resource.Result, error) { From 0e4dfe1baf803e39db24bdb4e6f0fc06617751bb Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Mon, 25 Aug 2025 17:19:11 +0300 Subject: [PATCH 08/44] added the flag_swap_transaction when a swap needs to be performed in the send node --- services/registration/pp.csv | 1 + 1 file changed, 1 insertion(+) diff --git a/services/registration/pp.csv b/services/registration/pp.csv index 1e476c1..497f44b 100644 --- a/services/registration/pp.csv +++ b/services/registration/pp.csv @@ -35,3 +35,4 @@ flag,flag_account_pin_reset,41,this is set on an account when an admin triggers 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 flag,flag_alias_unavailable,44,this is set when the preferred alias is not available +flag,flag_swap_transaction,45,this is set when the transaction will involve performing a swap From 14d493475e8d69120af91b01704480107f6290d2 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Mon, 25 Aug 2025 17:20:07 +0300 Subject: [PATCH 09/44] catch the flag_swap_transaction and move to the transaction_swap node --- services/registration/amount.vis | 1 + services/registration/transaction_swap | 3 +++ services/registration/transaction_swap.vis | 12 ++++++++++++ 3 files changed, 16 insertions(+) create mode 100644 services/registration/transaction_swap create mode 100644 services/registration/transaction_swap.vis diff --git a/services/registration/amount.vis b/services/registration/amount.vis index c50691f..d4786ab 100644 --- a/services/registration/amount.vis +++ b/services/registration/amount.vis @@ -4,6 +4,7 @@ RELOAD max_amount MAP max_amount MOUT back 0 HALT +CATCH transaction_swap flag_swap_transaction 1 LOAD validate_amount 64 RELOAD validate_amount CATCH api_failure flag_api_call_error 1 diff --git a/services/registration/transaction_swap b/services/registration/transaction_swap new file mode 100644 index 0000000..4120576 --- /dev/null +++ b/services/registration/transaction_swap @@ -0,0 +1,3 @@ +{{.transaction_swap_preview}} + +Please enter your PIN to confirm: \ No newline at end of file diff --git a/services/registration/transaction_swap.vis b/services/registration/transaction_swap.vis new file mode 100644 index 0000000..7f2f53e --- /dev/null +++ b/services/registration/transaction_swap.vis @@ -0,0 +1,12 @@ +LOAD transaction_swap_preview 0 +MAP transaction_swap_preview +CATCH api_failure flag_api_call_error 1 +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 transaction_swap_initiated * From 0c67efedea4f2577fa24a446c0acdc8fc59d16a0 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Mon, 25 Aug 2025 17:20:34 +0300 Subject: [PATCH 10/44] added the transaction_swap_initiated node --- services/registration/transaction_swap_initiated.vis | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 services/registration/transaction_swap_initiated.vis diff --git a/services/registration/transaction_swap_initiated.vis b/services/registration/transaction_swap_initiated.vis new file mode 100644 index 0000000..d6c0f57 --- /dev/null +++ b/services/registration/transaction_swap_initiated.vis @@ -0,0 +1,4 @@ +LOAD reset_incorrect_pin 6 +CATCH _ flag_account_authorized 0 +LOAD transaction_initiate_swap 0 +HALT From 5a09d33be0dc2fd0230640813a3f9f7bce71d7a3 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Mon, 25 Aug 2025 17:21:23 +0300 Subject: [PATCH 11/44] removed unused DATA_RECIPIENT_ACTIVE_TOKEN key --- store/db/db.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/store/db/db.go b/store/db/db.go index c21a645..4cc3f57 100644 --- a/store/db/db.go +++ b/store/db/db.go @@ -91,8 +91,6 @@ const ( DATA_ACTIVE_POOL_SYM // Holds the send transaction type DATA_SEND_TRANSACTION_TYPE - // Holds the recipient active token (RAT) - DATA_RECIPIENT_ACTIVE_TOKEN // Holds the recipient formatted phone number DATA_RECIPIENT_PHONE_NUMBER ) From 0f8c2f9270d47b3ac811eae744fcffaea97a2c2b Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Mon, 25 Aug 2025 17:24:58 +0300 Subject: [PATCH 12/44] use the recipient's phone number to read swap related data --- handlers/application/send.go | 76 +++++++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 22 deletions(-) diff --git a/handlers/application/send.go b/handlers/application/send.go index 1bdae5d..f555a0c 100644 --- a/handlers/application/send.go +++ b/handlers/application/send.go @@ -14,6 +14,7 @@ import ( "git.grassecon.net/grassrootseconomics/sarafu-vise/store" storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" "github.com/grassrootseconomics/ethutils" + dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" "gopkg.in/leonelquinteros/gotext.v1" ) @@ -89,20 +90,20 @@ func (h *MenuHandlers) handlePhoneNumber(ctx context.Context, sessionId, recipie return *res, err } - senderSym, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM) + senderActiveAddress, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS) if err != nil { - logg.ErrorCtxf(ctx, "Failed to read sender activeSym", "error", err) + logg.ErrorCtxf(ctx, "Failed to read sender senderActiveAddress", "error", err) return *res, err } - recipientActiveToken, _ := store.ReadEntry(ctx, formattedNumber, storedb.DATA_ACTIVE_SYM) + recipientActiveAddress, _ := store.ReadEntry(ctx, formattedNumber, storedb.DATA_ACTIVE_ADDRESS) txType := "swap" // recipient has no active token → normal transaction - if recipientActiveToken == nil { + if recipientActiveAddress == nil { txType = "normal" - } else if senderSym != nil && string(senderSym) == string(recipientActiveToken) { + } else if senderActiveAddress != nil && string(senderActiveAddress) == string(recipientActiveAddress) { // recipient has active token same as sender → normal transaction txType = "normal" } @@ -113,12 +114,10 @@ func (h *MenuHandlers) handlePhoneNumber(ctx context.Context, sessionId, recipie return *res, err } - // only save recipient’s active token if it exists - if recipientActiveToken != nil { - if err := store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT_ACTIVE_TOKEN, recipientActiveToken); err != nil { - logg.ErrorCtxf(ctx, "Failed to write recipient active token", "error", err) - return *res, err - } + // save the recipient's phone number + if err := store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT_PHONE_NUMBER, []byte(formattedNumber)); err != nil { + logg.ErrorCtxf(ctx, "Failed to write recipient's phone number", "type", txType, "error", err) + return *res, err } return *res, nil @@ -219,7 +218,7 @@ func (h *MenuHandlers) TransactionReset(ctx context.Context, sym string, input [ return res, nil } - err = store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT_ACTIVE_TOKEN, []byte("")) + err = store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT_PHONE_NUMBER, []byte("")) if err != nil { return res, nil } @@ -264,6 +263,7 @@ func (h *MenuHandlers) MaxAmount(ctx context.Context, sym string, input []byte) } flag_api_error, _ := h.flagManager.GetFlag("flag_api_error") + flag_swap_transaction, _ := h.flagManager.GetFlag("flag_swap_transaction") userStore := h.userdataStore // Fetch session data @@ -277,16 +277,22 @@ func (h *MenuHandlers) MaxAmount(ctx context.Context, sym string, input []byte) // If normal transaction, return balance if string(transactionType) == "normal" { + res.FlagReset = append(res.FlagReset, flag_swap_transaction) res.Content = fmt.Sprintf("%s %s", formattedBalance, string(activeSym)) return res, nil } - // Get recipient token address - recipientTokenAddress, err := h.getRecipientTokenAddress(ctx, sessionId) + res.FlagSet = append(res.FlagSet, flag_swap_transaction) + + // Get the recipient's phone number to read other data items + recipientPhoneNumber, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_RECIPIENT_PHONE_NUMBER) if err != nil { - // fallback to normal - res.Content = fmt.Sprintf("%s %s", formattedBalance, string(activeSym)) - return res, nil + // invalid state + return res, err + } + recipientActiveSym, recipientActiveAddress, recipientActiveDecimal, err := h.getRecipientData(ctx, string(recipientPhoneNumber)) + if err != nil { + return res, err } // Resolve active pool address @@ -307,7 +313,7 @@ func (h *MenuHandlers) MaxAmount(ctx context.Context, sym string, input []byte) } // Calculate max swappable amount - maxStr, err := h.calculateSwapMaxAmount(ctx, activePoolAddress, activeAddress, recipientTokenAddress, publicKey, activeDecimal) + maxStr, err := h.calculateSwapMaxAmount(ctx, activePoolAddress, activeAddress, recipientActiveAddress, publicKey, activeDecimal) if err != nil { res.FlagSet = append(res.FlagSet, flag_api_error) return res, err @@ -327,6 +333,20 @@ func (h *MenuHandlers) MaxAmount(ctx context.Context, sym string, input []byte) return res, err } + // save swap related data for the swap preview + metadata := &dataserviceapi.TokenHoldings{ + TokenSymbol: string(recipientActiveSym), + Balance: formattedBalance, //not used + TokenDecimals: string(recipientActiveDecimal), + TokenAddress: string(recipientActiveAddress), + } + + // 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 + } + res.Content = fmt.Sprintf("%s %s", maxStr, string(activeSym)) return res, nil } @@ -355,16 +375,28 @@ func (h *MenuHandlers) getSessionData(ctx context.Context, sessionId string) (tr return } activeDecimal, err = store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_DECIMAL) + if err != nil { + return + } return } -func (h *MenuHandlers) getRecipientTokenAddress(ctx context.Context, sessionId string) ([]byte, error) { +func (h *MenuHandlers) getRecipientData(ctx context.Context, sessionId string) (recipientActiveSym, recipientActiveAddress, recipientActiveDecimal []byte, err error) { store := h.userdataStore - recipientPhone, err := store.ReadEntry(ctx, sessionId, storedb.DATA_RECIPIENT_PHONE_NUMBER) + + recipientActiveSym, err = store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM) if err != nil { - return nil, err + return } - return store.ReadEntry(ctx, string(recipientPhone), storedb.DATA_ACTIVE_ADDRESS) + recipientActiveAddress, err = store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS) + if err != nil { + return + } + recipientActiveDecimal, err = store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_DECIMAL) + if err != nil { + return + } + return } func (h *MenuHandlers) resolveActivePoolAddress(ctx context.Context, sessionId string) ([]byte, error) { From 4492f8087a4b2e9a70293677b263235a132cc19c Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Mon, 25 Aug 2025 17:25:59 +0300 Subject: [PATCH 13/44] added TransactionSwapPreview functionality --- handlers/application/send.go | 101 +++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/handlers/application/send.go b/handlers/application/send.go index f555a0c..38ef181 100644 --- a/handlers/application/send.go +++ b/handlers/application/send.go @@ -602,3 +602,104 @@ func (h *MenuHandlers) InitiateTransaction(ctx context.Context, sym string, inpu res.FlagReset = append(res.FlagReset, flag_account_authorized) return res, nil } + +// TransactionSwapPreview displays the send swap preview and estimates +func (h *MenuHandlers) TransactionSwapPreview(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 + + recipientPhoneNumber, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_RECIPIENT_PHONE_NUMBER) + if err != nil { + // invalid state + return res, err + } + + swapData, err := store.ReadSwapPreviewData(ctx, userStore, sessionId) + if err != nil { + return res, err + } + + maxValue, err := strconv.ParseFloat(swapData.ActiveSwapMaxAmount, 64) + if err != nil { + logg.ErrorCtxf(ctx, "Failed to convert the swapMaxAmount to a float", "error", err) + return res, err + } + + inputAmount, err := strconv.ParseFloat(inputStr, 64) + if err != nil || inputAmount > maxValue { + res.FlagSet = append(res.FlagSet, flag_invalid_amount) + res.Content = inputStr + return res, nil + } + + // Format the amount to 2 decimal places + formattedAmount, err := store.TruncateDecimalString(inputStr, 2) + if err != nil { + res.FlagSet = append(res.FlagSet, flag_invalid_amount) + res.Content = inputStr + return res, nil + } + + finalAmountStr, err := store.ParseAndScaleAmount(formattedAmount, swapData.ActiveSwapFromDecimal) + if err != nil { + return res, err + } + + err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SWAP_AMOUNT, []byte(finalAmountStr)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write swap amount entry with", "key", storedb.DATA_ACTIVE_SWAP_AMOUNT, "value", finalAmountStr, "error", err) + return res, err + } + // store the user's input amount in the temporary value + err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(inputStr)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write swap amount entry with", "key", storedb.DATA_ACTIVE_SWAP_AMOUNT, "value", finalAmountStr, "error", err) + return res, err + } + + // call the API to get the quote + r, err := h.accountService.GetPoolSwapQuote(ctx, finalAmountStr, swapData.PublicKey, swapData.ActiveSwapFromAddress, swapData.ActivePoolAddress, swapData.ActiveSwapToAddress) + if err != nil { + flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") + res.FlagSet = append(res.FlagSet, flag_api_error) + res.Content = l.Get("Your request failed. Please try again later.") + logg.ErrorCtxf(ctx, "failed on poolSwap", "error", err) + return res, nil + } + + // Scale down the quoted amount + quoteAmountStr := store.ScaleDownBalance(r.OutValue, swapData.ActiveSwapToDecimal) + + fmt.Println("the quoteAmountStr is:", quoteAmountStr) + 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( + "%s will receive %s %s", + string(recipientPhoneNumber), qouteStr, swapData.ActiveSwapToSym, + ) + + return res, nil +} From cda2d49f3e52071c4becfc6911d61d1486716376 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Mon, 25 Aug 2025 17:27:20 +0300 Subject: [PATCH 14/44] added TransactionInitiateSwap functionality that performs a swap followed by a send --- handlers/application/send.go | 81 ++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/handlers/application/send.go b/handlers/application/send.go index 38ef181..49963af 100644 --- a/handlers/application/send.go +++ b/handlers/application/send.go @@ -703,3 +703,84 @@ func (h *MenuHandlers) TransactionSwapPreview(ctx context.Context, sym string, i return res, nil } + +// TransactionInitiateSwap calls the poolSwap and returns a confirmation based on the result. +func (h *MenuHandlers) TransactionInitiateSwap(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") + flag_swap_transaction, _ := h.flagManager.GetFlag("flag_swap_transaction") + + 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 + poolSwap, 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 + } + + swapTrackingId := poolSwap.TrackingId + logg.InfoCtxf(ctx, "poolSwap", "swapTrackingId", swapTrackingId) + + // Initiate a send + recipientPublicKey, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_RECIPIENT) + if err != nil { + logg.ErrorCtxf(ctx, "failed to read swapAmount entry with", "key", storedb.DATA_ACTIVE_SWAP_AMOUNT, "error", err) + return res, err + } + recipientPhoneNumber, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_RECIPIENT_PHONE_NUMBER) + if err != nil { + // invalid state + return res, err + } + + // Call TokenTransfer + tokenTransfer, err := h.accountService.TokenTransfer(ctx, swapAmountStr, swapData.PublicKey, string(recipientPublicKey), 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 TokenTransfer", "error", err) + return res, nil + } + + trackingId := tokenTransfer.TrackingId + logg.InfoCtxf(ctx, "TokenTransfer", "trackingId", trackingId) + + res.Content = l.Get( + "Your request has been sent. %s will receive %s %s from %s.", + string(recipientPhoneNumber), + swapData.TemporaryValue, + swapData.ActiveSwapToSym, + sessionId, + ) + + res.FlagReset = append(res.FlagReset, flag_account_authorized, flag_swap_transaction) + return res, nil +} From c90d3cd73104adcce9bf97ff9fd4a454ec553939 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Mon, 25 Aug 2025 17:28:11 +0300 Subject: [PATCH 15/44] added the handler functions --- handlers/local.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/handlers/local.go b/handlers/local.go index ed64020..4742041 100644 --- a/handlers/local.go +++ b/handlers/local.go @@ -136,6 +136,9 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountService) ls.DbRs.AddLocalFunc("swap_max_limit", appHandlers.SwapMaxLimit) ls.DbRs.AddLocalFunc("swap_preview", appHandlers.SwapPreview) ls.DbRs.AddLocalFunc("initiate_swap", appHandlers.InitiateSwap) + ls.DbRs.AddLocalFunc("transaction_swap_preview", appHandlers.TransactionSwapPreview) + ls.DbRs.AddLocalFunc("transaction_initiate_swap", appHandlers.TransactionInitiateSwap) + ls.first = appHandlers.Init return appHandlers, nil From f7859fb72a02941df571034cb6988e3a318b8475 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Thu, 9 Oct 2025 12:15:56 +0300 Subject: [PATCH 16/44] handle the transaction type for a swap when using an address --- handlers/application/send.go | 59 +++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/handlers/application/send.go b/handlers/application/send.go index 49963af..dc67ff0 100644 --- a/handlers/application/send.go +++ b/handlers/application/send.go @@ -8,6 +8,7 @@ import ( "git.defalsify.org/vise.git/db" "git.defalsify.org/vise.git/resource" + "git.grassecon.net/grassrootseconomics/common/hex" "git.grassecon.net/grassrootseconomics/common/identity" "git.grassecon.net/grassrootseconomics/common/phone" "git.grassecon.net/grassrootseconomics/sarafu-vise/config" @@ -125,17 +126,67 @@ func (h *MenuHandlers) handlePhoneNumber(ctx context.Context, sessionId, recipie func (h *MenuHandlers) handleAddress(ctx context.Context, sessionId, recipient string, res *resource.Result) (resource.Result, error) { store := h.userdataStore - address := ethutils.ChecksumAddress(recipient) + // set the default transaction type + txType := "swap" + + // use the received address as the recipient's public key if err := store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, []byte(address)); err != nil { logg.ErrorCtxf(ctx, "Failed to write recipient address", "error", err) return *res, err } - if err := store.WriteEntry(ctx, sessionId, storedb.DATA_SEND_TRANSACTION_TYPE, []byte("normal")); err != nil { - logg.ErrorCtxf(ctx, "Failed to write tx type for address", "error", err) + + // normalize the address to fetch the recipient's phone number + publicKeyNormalized, err := hex.NormalizeHex(address) + if err != nil { return *res, err } + // get the recipient's phone number from the address + recipientPhoneNumber, err := store.ReadEntry(ctx, publicKeyNormalized, storedb.DATA_PUBLIC_KEY_REVERSE) + if err != nil || len(recipientPhoneNumber) == 0 { + // Address not registered → fallback to normal transaction + txType = "normal" + logg.WarnCtxf(ctx, "Recipient address not registered, switching to normal transaction", "address", address) + recipientPhoneNumber = nil + } + + // Read sender's active address + senderActiveAddress, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS) + if err != nil { + logg.ErrorCtxf(ctx, "Failed to read sender active address", "error", err) + return *res, err + } + + var recipientActiveAddress []byte + if recipientPhoneNumber != nil { + recipientActiveAddress, _ = store.ReadEntry(ctx, string(recipientPhoneNumber), storedb.DATA_ACTIVE_ADDRESS) + } + + // recipient has no active token → normal transaction + if recipientActiveAddress == nil { + txType = "normal" + } else if senderActiveAddress != nil && string(senderActiveAddress) == string(recipientActiveAddress) { + // recipient has active token same as sender → normal transaction + txType = "normal" + } + + // Save the transaction type + if err := store.WriteEntry(ctx, sessionId, storedb.DATA_SEND_TRANSACTION_TYPE, []byte(txType)); err != nil { + logg.ErrorCtxf(ctx, "Failed to write transaction type", "type", txType, "error", err) + return *res, err + } + + // Save the recipient's phone number only if it exists + if recipientPhoneNumber != nil { + if err := store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT_PHONE_NUMBER, recipientPhoneNumber); err != nil { + logg.ErrorCtxf(ctx, "Failed to write recipient phone number", "error", err) + return *res, err + } + } else { + logg.InfoCtxf(ctx, "No recipient phone number found for address", "address", address) + } + return *res, nil } @@ -748,7 +799,7 @@ func (h *MenuHandlers) TransactionInitiateSwap(ctx context.Context, sym string, swapTrackingId := poolSwap.TrackingId logg.InfoCtxf(ctx, "poolSwap", "swapTrackingId", swapTrackingId) - // Initiate a send + // Initiate a send recipientPublicKey, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_RECIPIENT) if err != nil { logg.ErrorCtxf(ctx, "failed to read swapAmount entry with", "key", storedb.DATA_ACTIVE_SWAP_AMOUNT, "error", err) From 27fdb209749b84a7c893938f3588d73b606dfda5 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Thu, 9 Oct 2025 12:27:51 +0300 Subject: [PATCH 17/44] handle the transaction type for a swap when using an alias --- handlers/application/send.go | 63 ++++++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 7 deletions(-) diff --git a/handlers/application/send.go b/handlers/application/send.go index dc67ff0..8eccca7 100644 --- a/handlers/application/send.go +++ b/handlers/application/send.go @@ -195,12 +195,14 @@ func (h *MenuHandlers) handleAlias(ctx context.Context, sessionId, recipient str flag_invalid_recipient, _ := h.flagManager.GetFlag("flag_invalid_recipient") flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") - var AliasAddressResult string + var aliasAddressResult string + // set the default transaction type + txType := "swap" if strings.Contains(recipient, ".") { alias, err := h.accountService.CheckAliasAddress(ctx, recipient) if err == nil { - AliasAddressResult = alias.Address + aliasAddressResult = alias.Address } else { logg.ErrorCtxf(ctx, "Failed to resolve alias", "alias", recipient, "error", err) } @@ -212,7 +214,7 @@ func (h *MenuHandlers) handleAlias(ctx context.Context, sessionId, recipient str alias, err := h.accountService.CheckAliasAddress(ctx, fqdn) if err == nil { res.FlagReset = append(res.FlagReset, flag_api_error) - AliasAddressResult = alias.Address + aliasAddressResult = alias.Address break } else { res.FlagSet = append(res.FlagSet, flag_api_error) @@ -222,21 +224,68 @@ func (h *MenuHandlers) handleAlias(ctx context.Context, sessionId, recipient str } } - if AliasAddressResult == "" { + if aliasAddressResult == "" { res.FlagSet = append(res.FlagSet, flag_invalid_recipient) res.Content = recipient return *res, nil } - if err := store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, []byte(AliasAddressResult)); err != nil { + if err := store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, []byte(aliasAddressResult)); err != nil { logg.ErrorCtxf(ctx, "Failed to store alias recipient", "error", err) return *res, err } - if err := store.WriteEntry(ctx, sessionId, storedb.DATA_SEND_TRANSACTION_TYPE, []byte("normal")); err != nil { - logg.ErrorCtxf(ctx, "Failed to write tx type for alias", "error", err) + + // Normalize the alias address to fetch the recipient's phone number + publicKeyNormalized, err := hex.NormalizeHex(aliasAddressResult) + if err != nil { + logg.ErrorCtxf(ctx, "Failed to normalize alias address", "address", aliasAddressResult, "error", err) return *res, err } + // get the recipient's phone number from the address + recipientPhoneNumber, err := store.ReadEntry(ctx, publicKeyNormalized, storedb.DATA_PUBLIC_KEY_REVERSE) + if err != nil || len(recipientPhoneNumber) == 0 { + txType = "normal" + logg.WarnCtxf(ctx, "Alias address not registered, switching to normal transaction", "address", aliasAddressResult) + recipientPhoneNumber = nil + } + + // Read sender's active address + senderActiveAddress, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS) + if err != nil { + logg.ErrorCtxf(ctx, "Failed to read sender active address", "error", err) + return *res, err + } + + var recipientActiveAddress []byte + if recipientPhoneNumber != nil { + recipientActiveAddress, _ = store.ReadEntry(ctx, string(recipientPhoneNumber), storedb.DATA_ACTIVE_ADDRESS) + } + + // recipient has no active token → normal transaction + if recipientActiveAddress == nil { + txType = "normal" + } else if senderActiveAddress != nil && string(senderActiveAddress) == string(recipientActiveAddress) { + // recipient has active token same as sender → normal transaction + txType = "normal" + } + + // Save the transaction type + if err := store.WriteEntry(ctx, sessionId, storedb.DATA_SEND_TRANSACTION_TYPE, []byte(txType)); err != nil { + logg.ErrorCtxf(ctx, "Failed to write transaction type", "type", txType, "error", err) + return *res, err + } + + // Save the recipient's phone number only if it exists + if recipientPhoneNumber != nil { + if err := store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT_PHONE_NUMBER, recipientPhoneNumber); err != nil { + logg.ErrorCtxf(ctx, "Failed to write recipient phone number", "error", err) + return *res, err + } + } else { + logg.InfoCtxf(ctx, "No recipient phone number found for alias", "address", aliasAddressResult) + } + return *res, nil } From fb5eb3f24fb0608ba5f61a61ca43d8405d26cf27 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Thu, 9 Oct 2025 12:45:16 +0300 Subject: [PATCH 18/44] added the determineAndSaveTransactionType heper for shared logic on the transaction type --- handlers/application/send.go | 101 ++++++++++------------------------- 1 file changed, 27 insertions(+), 74 deletions(-) diff --git a/handlers/application/send.go b/handlers/application/send.go index 8eccca7..8195f66 100644 --- a/handlers/application/send.go +++ b/handlers/application/send.go @@ -91,33 +91,8 @@ func (h *MenuHandlers) handlePhoneNumber(ctx context.Context, sessionId, recipie return *res, err } - senderActiveAddress, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS) - if err != nil { - logg.ErrorCtxf(ctx, "Failed to read sender senderActiveAddress", "error", err) - return *res, err - } - - recipientActiveAddress, _ := store.ReadEntry(ctx, formattedNumber, storedb.DATA_ACTIVE_ADDRESS) - - txType := "swap" - - // recipient has no active token → normal transaction - if recipientActiveAddress == nil { - txType = "normal" - } else if senderActiveAddress != nil && string(senderActiveAddress) == string(recipientActiveAddress) { - // recipient has active token same as sender → normal transaction - txType = "normal" - } - - // save transaction type - if err := store.WriteEntry(ctx, sessionId, storedb.DATA_SEND_TRANSACTION_TYPE, []byte(txType)); err != nil { - logg.ErrorCtxf(ctx, "Failed to write tx type", "type", txType, "error", err) - return *res, err - } - - // save the recipient's phone number - if err := store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT_PHONE_NUMBER, []byte(formattedNumber)); err != nil { - logg.ErrorCtxf(ctx, "Failed to write recipient's phone number", "type", txType, "error", err) + // Delegate to shared logic + if err := h.determineAndSaveTransactionType(ctx, sessionId, publicKey, []byte(formattedNumber)); err != nil { return *res, err } @@ -127,10 +102,7 @@ func (h *MenuHandlers) handlePhoneNumber(ctx context.Context, sessionId, recipie func (h *MenuHandlers) handleAddress(ctx context.Context, sessionId, recipient string, res *resource.Result) (resource.Result, error) { store := h.userdataStore address := ethutils.ChecksumAddress(recipient) - // set the default transaction type - txType := "swap" - // use the received address as the recipient's public key if err := store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, []byte(address)); err != nil { logg.ErrorCtxf(ctx, "Failed to write recipient address", "error", err) return *res, err @@ -145,48 +117,14 @@ func (h *MenuHandlers) handleAddress(ctx context.Context, sessionId, recipient s // get the recipient's phone number from the address recipientPhoneNumber, err := store.ReadEntry(ctx, publicKeyNormalized, storedb.DATA_PUBLIC_KEY_REVERSE) if err != nil || len(recipientPhoneNumber) == 0 { - // Address not registered → fallback to normal transaction - txType = "normal" logg.WarnCtxf(ctx, "Recipient address not registered, switching to normal transaction", "address", address) recipientPhoneNumber = nil } - // Read sender's active address - senderActiveAddress, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS) - if err != nil { - logg.ErrorCtxf(ctx, "Failed to read sender active address", "error", err) + if err := h.determineAndSaveTransactionType(ctx, sessionId, []byte(address), recipientPhoneNumber); err != nil { return *res, err } - var recipientActiveAddress []byte - if recipientPhoneNumber != nil { - recipientActiveAddress, _ = store.ReadEntry(ctx, string(recipientPhoneNumber), storedb.DATA_ACTIVE_ADDRESS) - } - - // recipient has no active token → normal transaction - if recipientActiveAddress == nil { - txType = "normal" - } else if senderActiveAddress != nil && string(senderActiveAddress) == string(recipientActiveAddress) { - // recipient has active token same as sender → normal transaction - txType = "normal" - } - - // Save the transaction type - if err := store.WriteEntry(ctx, sessionId, storedb.DATA_SEND_TRANSACTION_TYPE, []byte(txType)); err != nil { - logg.ErrorCtxf(ctx, "Failed to write transaction type", "type", txType, "error", err) - return *res, err - } - - // Save the recipient's phone number only if it exists - if recipientPhoneNumber != nil { - if err := store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT_PHONE_NUMBER, recipientPhoneNumber); err != nil { - logg.ErrorCtxf(ctx, "Failed to write recipient phone number", "error", err) - return *res, err - } - } else { - logg.InfoCtxf(ctx, "No recipient phone number found for address", "address", address) - } - return *res, nil } @@ -196,8 +134,6 @@ func (h *MenuHandlers) handleAlias(ctx context.Context, sessionId, recipient str flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") var aliasAddressResult string - // set the default transaction type - txType := "swap" if strings.Contains(recipient, ".") { alias, err := h.accountService.CheckAliasAddress(ctx, recipient) @@ -245,16 +181,33 @@ func (h *MenuHandlers) handleAlias(ctx context.Context, sessionId, recipient str // get the recipient's phone number from the address recipientPhoneNumber, err := store.ReadEntry(ctx, publicKeyNormalized, storedb.DATA_PUBLIC_KEY_REVERSE) if err != nil || len(recipientPhoneNumber) == 0 { - txType = "normal" logg.WarnCtxf(ctx, "Alias address not registered, switching to normal transaction", "address", aliasAddressResult) recipientPhoneNumber = nil } + if err := h.determineAndSaveTransactionType(ctx, sessionId, []byte(aliasAddressResult), recipientPhoneNumber); err != nil { + return *res, err + } + + return *res, nil +} + +// determineAndSaveTransactionType centralizes transaction-type logic and recipient info persistence. +// It expects the session to already have the recipient's public key (address) written. +func (h *MenuHandlers) determineAndSaveTransactionType( + ctx context.Context, + sessionId string, + publicKey []byte, + recipientPhoneNumber []byte, +) error { + store := h.userdataStore + txType := "swap" + // Read sender's active address senderActiveAddress, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS) if err != nil { logg.ErrorCtxf(ctx, "Failed to read sender active address", "error", err) - return *res, err + return err } var recipientActiveAddress []byte @@ -273,20 +226,20 @@ func (h *MenuHandlers) handleAlias(ctx context.Context, sessionId, recipient str // Save the transaction type if err := store.WriteEntry(ctx, sessionId, storedb.DATA_SEND_TRANSACTION_TYPE, []byte(txType)); err != nil { logg.ErrorCtxf(ctx, "Failed to write transaction type", "type", txType, "error", err) - return *res, err + return err } // Save the recipient's phone number only if it exists if recipientPhoneNumber != nil { if err := store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT_PHONE_NUMBER, recipientPhoneNumber); err != nil { - logg.ErrorCtxf(ctx, "Failed to write recipient phone number", "error", err) - return *res, err + logg.ErrorCtxf(ctx, "Failed to write recipient phone number", "type", txType, "error", err) + return err } } else { - logg.InfoCtxf(ctx, "No recipient phone number found for alias", "address", aliasAddressResult) + logg.InfoCtxf(ctx, "No recipient phone number found for public key", "publicKey", string(publicKey)) } - return *res, nil + return nil } // TransactionReset resets the previous transaction data (Recipient and Amount) From 764431ee58742c870024cc69d0d1dead16bae1ad Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Thu, 9 Oct 2025 13:08:22 +0300 Subject: [PATCH 19/44] added some debug statements --- handlers/application/send.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/handlers/application/send.go b/handlers/application/send.go index 8195f66..95b7f4c 100644 --- a/handlers/application/send.go +++ b/handlers/application/send.go @@ -121,6 +121,8 @@ func (h *MenuHandlers) handleAddress(ctx context.Context, sessionId, recipient s recipientPhoneNumber = nil } + fmt.Println("on the address; address:", address, "publicKeyNormalized", publicKeyNormalized, "recipientPhoneNumber", recipientPhoneNumber) + if err := h.determineAndSaveTransactionType(ctx, sessionId, []byte(address), recipientPhoneNumber); err != nil { return *res, err } @@ -185,6 +187,8 @@ func (h *MenuHandlers) handleAlias(ctx context.Context, sessionId, recipient str recipientPhoneNumber = nil } + fmt.Println("on the alias; address:", aliasAddressResult, "publicKeyNormalized", publicKeyNormalized, "recipientPhoneNumber", recipientPhoneNumber) + if err := h.determineAndSaveTransactionType(ctx, sessionId, []byte(aliasAddressResult), recipientPhoneNumber); err != nil { return *res, err } @@ -223,6 +227,8 @@ func (h *MenuHandlers) determineAndSaveTransactionType( txType = "normal" } + fmt.Println("the final txtype:", txType) + // Save the transaction type if err := store.WriteEntry(ctx, sessionId, storedb.DATA_SEND_TRANSACTION_TYPE, []byte(txType)); err != nil { logg.ErrorCtxf(ctx, "Failed to write transaction type", "type", txType, "error", err) @@ -325,6 +331,8 @@ func (h *MenuHandlers) MaxAmount(ctx context.Context, sym string, input []byte) return res, err } + fmt.Println("the transaction type:", transactionType) + // Format the active balance amount to 2 decimal places formattedBalance, _ := store.TruncateDecimalString(string(activeBal), 2) From ed780659d3a06f3dc56248395fc7da621bcc0b8d Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Thu, 9 Oct 2025 13:20:58 +0300 Subject: [PATCH 20/44] removed the debug statements --- handlers/application/send.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/handlers/application/send.go b/handlers/application/send.go index 95b7f4c..31b252c 100644 --- a/handlers/application/send.go +++ b/handlers/application/send.go @@ -121,8 +121,6 @@ func (h *MenuHandlers) handleAddress(ctx context.Context, sessionId, recipient s recipientPhoneNumber = nil } - fmt.Println("on the address; address:", address, "publicKeyNormalized", publicKeyNormalized, "recipientPhoneNumber", recipientPhoneNumber) - if err := h.determineAndSaveTransactionType(ctx, sessionId, []byte(address), recipientPhoneNumber); err != nil { return *res, err } @@ -187,8 +185,6 @@ func (h *MenuHandlers) handleAlias(ctx context.Context, sessionId, recipient str recipientPhoneNumber = nil } - fmt.Println("on the alias; address:", aliasAddressResult, "publicKeyNormalized", publicKeyNormalized, "recipientPhoneNumber", recipientPhoneNumber) - if err := h.determineAndSaveTransactionType(ctx, sessionId, []byte(aliasAddressResult), recipientPhoneNumber); err != nil { return *res, err } @@ -227,8 +223,6 @@ func (h *MenuHandlers) determineAndSaveTransactionType( txType = "normal" } - fmt.Println("the final txtype:", txType) - // Save the transaction type if err := store.WriteEntry(ctx, sessionId, storedb.DATA_SEND_TRANSACTION_TYPE, []byte(txType)); err != nil { logg.ErrorCtxf(ctx, "Failed to write transaction type", "type", txType, "error", err) @@ -331,8 +325,6 @@ func (h *MenuHandlers) MaxAmount(ctx context.Context, sym string, input []byte) return res, err } - fmt.Println("the transaction type:", transactionType) - // Format the active balance amount to 2 decimal places formattedBalance, _ := store.TruncateDecimalString(string(activeBal), 2) @@ -747,7 +739,6 @@ func (h *MenuHandlers) TransactionSwapPreview(ctx context.Context, sym string, i // Scale down the quoted amount quoteAmountStr := store.ScaleDownBalance(r.OutValue, swapData.ActiveSwapToDecimal) - fmt.Println("the quoteAmountStr is:", quoteAmountStr) qouteAmount, err := strconv.ParseFloat(quoteAmountStr, 64) if err != nil { logg.ErrorCtxf(ctx, "failed to parse quoteAmountStr as float", "value", quoteAmountStr, "error", err) From 606a551f609255a1c26415e3ae562bf43508704b Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Thu, 9 Oct 2025 13:31:55 +0300 Subject: [PATCH 21/44] use the correct data keys on the test --- store/swap_test.go | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/store/swap_test.go b/store/swap_test.go index ceabe99..3c0f0ee 100644 --- a/store/swap_test.go +++ b/store/swap_test.go @@ -14,13 +14,13 @@ func TestReadSwapData(t *testing.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", + storedb.DATA_PUBLIC_KEY: publicKey, + storedb.DATA_ACTIVE_POOL_ADDRESS: "0x48a953cA5cf5298bc6f6Af3C608351f537AAcb9e", + storedb.DATA_ACTIVE_SYM: "AMANI", + storedb.DATA_ACTIVE_DECIMAL: "6", + storedb.DATA_ACTIVE_ADDRESS: "0xc7B78Ac9ACB9E025C8234621FC515bC58179dEAe", + storedb.DATA_ACTIVE_SWAP_TO_SYM: "cUSD", + storedb.DATA_ACTIVE_SWAP_TO_ADDRESS: "0x765DE816845861e75A25fCA122bb6898B8B1282a", } // Store the data @@ -53,15 +53,16 @@ func TestReadSwapPreviewData(t *testing.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", + storedb.DATA_TEMPORARY_VALUE: "temp", + storedb.DATA_PUBLIC_KEY: publicKey, + storedb.DATA_ACTIVE_SWAP_MAX_AMOUNT: "1339482", + storedb.DATA_ACTIVE_DECIMAL: "6", + storedb.DATA_ACTIVE_POOL_ADDRESS: "0x48a953cA5cf5298bc6f6Af3C608351f537AAcb9e", + storedb.DATA_ACTIVE_ADDRESS: "0xc7B78Ac9ACB9E025C8234621FC515bC58179dEAe", + storedb.DATA_ACTIVE_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 @@ -72,6 +73,7 @@ func TestReadSwapPreviewData(t *testing.T) { } expectedResult := SwapPreviewData{ + TemporaryValue: "temp", PublicKey: "0X13242618721", ActiveSwapMaxAmount: "1339482", ActiveSwapFromDecimal: "6", From 987874586134664f7b2e575225343a8c2d63b354 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Thu, 9 Oct 2025 13:39:30 +0300 Subject: [PATCH 22/44] fixed the TestCheckBalance by adjusting the expected result --- handlers/application/balance_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/application/balance_test.go b/handlers/application/balance_test.go index c8da2ea..6cf9d17 100644 --- a/handlers/application/balance_test.go +++ b/handlers/application/balance_test.go @@ -52,7 +52,7 @@ func TestCheckBalance(t *testing.T) { alias: "user72", activeSym: "SRF", activeBal: "10.967", - expectedResult: resource.Result{Content: "user72 balance: 10.96 SRF\n"}, + expectedResult: resource.Result{Content: "user72\nBalance: 10.96 SRF\n"}, expectError: false, }, } From e029a8cdc7504a1331745628d07e60ae31d69121 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Thu, 9 Oct 2025 14:19:03 +0300 Subject: [PATCH 23/44] improved the tests by updating the menu --- menutraversal_test/group_test.json | 106 +++++++++--------- .../profile_edit_start_familyname.json | 6 +- .../profile_edit_start_firstname.json | 6 +- .../profile_edit_start_gender.json | 6 +- .../profile_edit_start_location.json | 6 +- .../profile_edit_start_offerings.json | 6 +- .../profile_edit_start_yob.json | 6 +- .../profile_edit_when_adjacent_item_set.json | 6 +- menutraversal_test/test_setup.json | 12 +- 9 files changed, 80 insertions(+), 80 deletions(-) diff --git a/menutraversal_test/group_test.json b/menutraversal_test/group_test.json index 38c382e..587dc8a 100644 --- a/menutraversal_test/group_test.json +++ b/menutraversal_test/group_test.json @@ -5,19 +5,19 @@ "steps": [ { "input": "", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" }, { - "input": "2", + "input": "3", "expectedContent": "My vouchers\n1:Select voucher\n2:Voucher details\n0:Back" }, { "input": "1", - "expectedContent": "Select number or symbol from your vouchers:\n1SRF\n0:Back\n99:Quit" + "expectedContent": "Select number or symbol from your vouchers:\n1:SRF\n0:Back\n99:Quit" }, { "input": "", - "expectedContent": "Select number or symbol from your vouchers:\n1SRF\n0:Back\n99:Quit" + "expectedContent": "Select number or symbol from your vouchers:\n1:SRF\n0:Back\n99:Quit" }, { "input": "1", @@ -29,7 +29,7 @@ }, { "input": "0", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" } ] }, @@ -38,15 +38,15 @@ "steps": [ { "input": "", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" }, { - "input": "2", + "input": "3", "expectedContent": "My vouchers\n1:Select voucher\n2:Voucher details\n0:Back" }, { "input": "1", - "expectedContent": "Select number or symbol from your vouchers:\n1SRF\n0:Back\n99:Quit" + "expectedContent": "Select number or symbol from your vouchers:\n1:SRF\n0:Back\n99:Quit" }, { "input": "SRF", @@ -58,7 +58,7 @@ }, { "input": "0", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" } ] }, @@ -67,10 +67,10 @@ "steps": [ { "input": "", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" }, { - "input": "3", + "input": "5", "expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back" }, { @@ -95,7 +95,7 @@ }, { "input": "0", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" } ] }, @@ -104,10 +104,10 @@ "steps": [ { "input": "", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" }, { - "input": "3", + "input": "5", "expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back" }, { @@ -136,7 +136,7 @@ }, { "input": "0", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" } ] }, @@ -145,10 +145,10 @@ "steps": [ { "input": "", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" }, { - "input": "3", + "input": "5", "expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back" }, { @@ -177,7 +177,7 @@ }, { "input": "0", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" } ] }, @@ -186,10 +186,10 @@ "steps": [ { "input": "", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" }, { - "input": "3", + "input": "5", "expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back" }, { @@ -210,7 +210,7 @@ }, { "input": "0", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" } ] }, @@ -219,10 +219,10 @@ "steps": [ { "input": "", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" }, { - "input": "3", + "input": "5", "expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back" }, { @@ -235,7 +235,7 @@ }, { "input": "0", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" } ] }, @@ -244,10 +244,10 @@ "steps": [ { "input": "", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" }, { - "input": "3", + "input": "5", "expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back" }, { @@ -280,7 +280,7 @@ }, { "input": "0", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" } ] }, @@ -289,10 +289,10 @@ "steps": [ { "input": "", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" }, { - "input": "3", + "input": "5", "expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back" }, { @@ -325,7 +325,7 @@ }, { "input": "0", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" } ] }, @@ -334,10 +334,10 @@ "steps": [ { "input": "", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" }, { - "input": "3", + "input": "5", "expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back" }, { @@ -382,7 +382,7 @@ }, { "input": "0", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" } ] }, @@ -391,10 +391,10 @@ "steps": [ { "input": "", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" }, { - "input": "3", + "input": "5", "expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back" }, { @@ -419,7 +419,7 @@ }, { "input": "0", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" } ] }, @@ -428,10 +428,10 @@ "steps": [ { "input": "", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" }, { - "input": "3", + "input": "5", "expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back" }, { @@ -460,7 +460,7 @@ }, { "input": "0", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" } ] }, @@ -469,10 +469,10 @@ "steps": [ { "input": "", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" }, { - "input": "3", + "input": "5", "expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back" }, { @@ -497,7 +497,7 @@ }, { "input": "0", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" } ] }, @@ -506,10 +506,10 @@ "steps": [ { "input": "", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" }, { - "input": "3", + "input": "5", "expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back" }, { @@ -534,7 +534,7 @@ }, { "input": "0", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" } ] }, @@ -543,10 +543,10 @@ "steps": [ { "input": "", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" }, { - "input": "3", + "input": "5", "expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back" }, { @@ -571,7 +571,7 @@ }, { "input": "0", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" } ] }, @@ -580,10 +580,10 @@ "steps": [ { "input": "", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" }, { - "input": "3", + "input": "5", "expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back" }, { @@ -604,7 +604,7 @@ }, { "input": "0", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" } ] }, @@ -613,10 +613,10 @@ "steps": [ { "input": "", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" }, { - "input": "3", + "input": "5", "expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back" }, { diff --git a/menutraversal_test/profile_edit_start_familyname.json b/menutraversal_test/profile_edit_start_familyname.json index bb8bf0d..05e4d26 100644 --- a/menutraversal_test/profile_edit_start_familyname.json +++ b/menutraversal_test/profile_edit_start_familyname.json @@ -5,10 +5,10 @@ "steps": [ { "input": "", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" }, { - "input": "3", + "input": "5", "expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back" }, { @@ -49,7 +49,7 @@ }, { "input": "0", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" } ] } diff --git a/menutraversal_test/profile_edit_start_firstname.json b/menutraversal_test/profile_edit_start_firstname.json index 1fa2ca6..f3c4b3e 100644 --- a/menutraversal_test/profile_edit_start_firstname.json +++ b/menutraversal_test/profile_edit_start_firstname.json @@ -5,10 +5,10 @@ "steps": [ { "input": "", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" }, { - "input": "3", + "input": "5", "expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back" }, { @@ -53,7 +53,7 @@ }, { "input": "0", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" } ] } diff --git a/menutraversal_test/profile_edit_start_gender.json b/menutraversal_test/profile_edit_start_gender.json index d95420f..abfafc3 100644 --- a/menutraversal_test/profile_edit_start_gender.json +++ b/menutraversal_test/profile_edit_start_gender.json @@ -5,10 +5,10 @@ "steps": [ { "input": "", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" }, { - "input": "3", + "input": "5", "expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back" }, { @@ -45,7 +45,7 @@ }, { "input": "0", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" } ] } diff --git a/menutraversal_test/profile_edit_start_location.json b/menutraversal_test/profile_edit_start_location.json index 86541a3..184f9ab 100644 --- a/menutraversal_test/profile_edit_start_location.json +++ b/menutraversal_test/profile_edit_start_location.json @@ -5,10 +5,10 @@ "steps": [ { "input": "", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" }, { - "input": "3", + "input": "5", "expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back" }, { @@ -37,7 +37,7 @@ }, { "input": "0", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" } ] } diff --git a/menutraversal_test/profile_edit_start_offerings.json b/menutraversal_test/profile_edit_start_offerings.json index 2fc976a..b49b4d6 100644 --- a/menutraversal_test/profile_edit_start_offerings.json +++ b/menutraversal_test/profile_edit_start_offerings.json @@ -5,10 +5,10 @@ "steps": [ { "input": "", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" }, { - "input": "3", + "input": "5", "expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back" }, { @@ -33,7 +33,7 @@ }, { "input": "0", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" } ] } diff --git a/menutraversal_test/profile_edit_start_yob.json b/menutraversal_test/profile_edit_start_yob.json index 10c1d11..0b09357 100644 --- a/menutraversal_test/profile_edit_start_yob.json +++ b/menutraversal_test/profile_edit_start_yob.json @@ -5,10 +5,10 @@ "steps": [ { "input": "", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" }, { - "input": "3", + "input": "5", "expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back" }, { @@ -41,7 +41,7 @@ }, { "input": "0", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" } ] } diff --git a/menutraversal_test/profile_edit_when_adjacent_item_set.json b/menutraversal_test/profile_edit_when_adjacent_item_set.json index ec7c880..8df7c4d 100644 --- a/menutraversal_test/profile_edit_when_adjacent_item_set.json +++ b/menutraversal_test/profile_edit_when_adjacent_item_set.json @@ -5,10 +5,10 @@ "steps": [ { "input": "", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" }, { - "input": "3", + "input": "5", "expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back" }, { @@ -61,7 +61,7 @@ }, { "input": "0", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" } ] } diff --git a/menutraversal_test/test_setup.json b/menutraversal_test/test_setup.json index 761760a..eb2f41d 100644 --- a/menutraversal_test/test_setup.json +++ b/menutraversal_test/test_setup.json @@ -57,7 +57,7 @@ "steps": [ { "input": "", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" }, { "input": "1", @@ -86,10 +86,10 @@ "steps": [ { "input": "", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" }, { - "input": "4", + "input": "6", "expectedContent": "For more help,please call: 0757628885" } ] @@ -99,7 +99,7 @@ "steps": [ { "input": "", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" }, { "input": "9", @@ -112,10 +112,10 @@ "steps": [ { "input": "", - "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + "expectedContent": "{balance}\n\n1:Send\n2:Swap\n3:My Vouchers\n4:Select pool\n5:My Account\n6:Help\n9:Quit" }, { - "input": "3", + "input": "5", "expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back" }, { From ba9a23946f7290271f5dedd4b85e91b28a9a22a3 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Thu, 9 Oct 2025 15:08:51 +0300 Subject: [PATCH 24/44] added ClearTransactionTypeFlag when a user goes back --- handlers/application/send.go | 16 ++++++++++++++++ handlers/local.go | 1 + services/registration/amount.vis | 2 ++ 3 files changed, 19 insertions(+) diff --git a/handlers/application/send.go b/handlers/application/send.go index 31b252c..814a6f9 100644 --- a/handlers/application/send.go +++ b/handlers/application/send.go @@ -836,3 +836,19 @@ func (h *MenuHandlers) TransactionInitiateSwap(ctx context.Context, sym string, res.FlagReset = append(res.FlagReset, flag_account_authorized, flag_swap_transaction) return res, nil } + + +// ClearTransactionTypeFlag resets the flag when a user goes back. +func (h *MenuHandlers) ClearTransactionTypeFlag(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + + flag_swap_transaction, _ := h.flagManager.GetFlag("flag_swap_transaction") + + inputStr := string(input) + if inputStr == "0" { + res.FlagReset = append(res.FlagReset, flag_swap_transaction) + return res, nil + } + + return res, nil +} diff --git a/handlers/local.go b/handlers/local.go index 4742041..836db72 100644 --- a/handlers/local.go +++ b/handlers/local.go @@ -138,6 +138,7 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountService) ls.DbRs.AddLocalFunc("initiate_swap", appHandlers.InitiateSwap) ls.DbRs.AddLocalFunc("transaction_swap_preview", appHandlers.TransactionSwapPreview) ls.DbRs.AddLocalFunc("transaction_initiate_swap", appHandlers.TransactionInitiateSwap) + ls.DbRs.AddLocalFunc("clear_trans_type_flag", appHandlers.ClearTransactionTypeFlag) ls.first = appHandlers.Init diff --git a/services/registration/amount.vis b/services/registration/amount.vis index d4786ab..e576600 100644 --- a/services/registration/amount.vis +++ b/services/registration/amount.vis @@ -4,6 +4,8 @@ RELOAD max_amount MAP max_amount MOUT back 0 HALT +LOAD clear_trans_type_flag 6 +RELOAD clear_trans_type_flag CATCH transaction_swap flag_swap_transaction 1 LOAD validate_amount 64 RELOAD validate_amount From 865dae4b7f990c1ec9c4e44a21e4a0f8aebcb990 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Wed, 22 Oct 2025 16:16:05 +0300 Subject: [PATCH 25/44] correctly format amounts to 2 decimal places using TruncateDecimalString --- handlers/application/poolswap.go | 9 ++------- handlers/application/send.go | 14 ++++---------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/handlers/application/poolswap.go b/handlers/application/poolswap.go index a226d33..6f08978 100644 --- a/handlers/application/poolswap.go +++ b/handlers/application/poolswap.go @@ -220,7 +220,7 @@ func (h *MenuHandlers) SwapMaxLimit(ctx context.Context, sym string, input []byt } // Format to 2 decimal places - maxStr := fmt.Sprintf("%.2f", maxAmountFloat) + maxStr, _ := store.TruncateDecimalString(string(maxAmountStr), 2) if maxAmountFloat < 0.1 { // return with low amount flag @@ -319,14 +319,9 @@ func (h *MenuHandlers) SwapPreview(ctx context.Context, sym string, input []byte // 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) + qouteStr, _ := store.TruncateDecimalString(string(quoteAmountStr), 2) res.Content = fmt.Sprintf( "You will swap:\n%s %s for %s %s:", diff --git a/handlers/application/send.go b/handlers/application/send.go index 110dd2b..715227f 100644 --- a/handlers/application/send.go +++ b/handlers/application/send.go @@ -751,19 +751,13 @@ func (h *MenuHandlers) TransactionSwapPreview(ctx context.Context, sym string, i // 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) + + // Format the qouteAmount amount to 2 decimal places + qouteAmount, _ := store.TruncateDecimalString(quoteAmountStr, 2) res.Content = fmt.Sprintf( "%s will receive %s %s", - string(recipientPhoneNumber), qouteStr, swapData.ActiveSwapToSym, + string(recipientPhoneNumber), qouteAmount, swapData.ActiveSwapToSym, ) return res, nil From 5cfdd949ffb90791ccd3f3d18693e7f14cf71c0f Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Wed, 22 Oct 2025 17:32:35 +0300 Subject: [PATCH 26/44] use the correct amount when initiating the transfer (the amount that has been swapped) --- handlers/application/send.go | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/handlers/application/send.go b/handlers/application/send.go index 715227f..7d47115 100644 --- a/handlers/application/send.go +++ b/handlers/application/send.go @@ -732,13 +732,7 @@ func (h *MenuHandlers) TransactionSwapPreview(ctx context.Context, sym string, i logg.ErrorCtxf(ctx, "failed to write swap amount entry with", "key", storedb.DATA_ACTIVE_SWAP_AMOUNT, "value", finalAmountStr, "error", err) return res, err } - // store the user's input amount in the temporary value - err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(inputStr)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write swap amount entry with", "key", storedb.DATA_ACTIVE_SWAP_AMOUNT, "value", finalAmountStr, "error", err) - return res, err - } - + // call the API to get the quote r, err := h.accountService.GetPoolSwapQuote(ctx, finalAmountStr, swapData.PublicKey, swapData.ActiveSwapFromAddress, swapData.ActivePoolAddress, swapData.ActiveSwapToAddress) if err != nil { @@ -749,12 +743,26 @@ func (h *MenuHandlers) TransactionSwapPreview(ctx context.Context, sym string, i return res, nil } + // store the outvalue as the final amount + err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_AMOUNT, []byte(r.OutValue)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write amount value entry with", "key", storedb.DATA_AMOUNT, "value", r.OutValue, "error", err) + return res, err + } + // Scale down the quoted amount quoteAmountStr := store.ScaleDownBalance(r.OutValue, swapData.ActiveSwapToDecimal) - + // Format the qouteAmount amount to 2 decimal places qouteAmount, _ := store.TruncateDecimalString(quoteAmountStr, 2) + // store the qouteAmount in the temporary value + err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(qouteAmount)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write temporary qouteAmount entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", qouteAmount, "error", err) + return res, err + } + res.Content = fmt.Sprintf( "%s will receive %s %s", string(recipientPhoneNumber), qouteAmount, swapData.ActiveSwapToSym, @@ -819,8 +827,15 @@ func (h *MenuHandlers) TransactionInitiateSwap(ctx context.Context, sym string, return res, err } - // Call TokenTransfer - tokenTransfer, err := h.accountService.TokenTransfer(ctx, swapAmountStr, swapData.PublicKey, string(recipientPublicKey), swapData.ActiveSwapToAddress) + // read the amount that should be sent + amount, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_AMOUNT) + if err != nil { + // invalid state + return res, err + } + + // Call TokenTransfer with the expected swap amount + tokenTransfer, err := h.accountService.TokenTransfer(ctx, string(amount), swapData.PublicKey, string(recipientPublicKey), swapData.ActiveSwapToAddress) if err != nil { flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") res.FlagSet = append(res.FlagSet, flag_api_error) From d9c49ee1192a7644f9b0cef936fca0033deaba63 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Tue, 28 Oct 2025 12:21:42 +0300 Subject: [PATCH 27/44] upgraded sarafu-api to make use of the credit-send API --- go.mod | 2 +- go.sum | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 6b8984b..17c2697 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.20250528124150-03bf7bfc1b66 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.20251022084613-532547899f63 + git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20251028083421-fe897cca84f2 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 e59d51f..e76c54f 100644 --- a/go.sum +++ b/go.sum @@ -24,6 +24,10 @@ git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20251021120522- git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20251021120522-6f7802b58cf5/go.mod h1:y/vsN8UO0wSxZk3gv0y5df4RPKMJI6TIxjVcVCPF8T8= git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20251022084613-532547899f63 h1:yznaUXeAy+qiZb2nCxosYXE5HyCPpynIoplEuYV/zQM= git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20251022084613-532547899f63/go.mod h1:y/vsN8UO0wSxZk3gv0y5df4RPKMJI6TIxjVcVCPF8T8= +git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20251028081048-a705443786fd h1:VIj5OdRae2wfE6NdLp6ZdHv0jtRbOeRURYQCU29RWBM= +git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20251028081048-a705443786fd/go.mod h1:y/vsN8UO0wSxZk3gv0y5df4RPKMJI6TIxjVcVCPF8T8= +git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20251028083421-fe897cca84f2 h1:wf//obTSLW5VZ0gM25l0U5oV/d+TBXX+1ClSMkEU7Uc= +git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20251028083421-fe897cca84f2/go.mod h1:y/vsN8UO0wSxZk3gv0y5df4RPKMJI6TIxjVcVCPF8T8= 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 8af2ccd36fe1f6887193b58eca2c21aceaeaaee3 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Tue, 28 Oct 2025 17:09:46 +0300 Subject: [PATCH 28/44] added text translations for the amounts (for normal and credit-send transactions) --- services/registration/locale/swa/default.po | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/services/registration/locale/swa/default.po b/services/registration/locale/swa/default.po index bf9f39a..18d6097 100644 --- a/services/registration/locale/swa/default.po +++ b/services/registration/locale/swa/default.po @@ -45,3 +45,10 @@ msgstr "Jina: %s\nSarafu: %s" msgid "Only USD vouchers are allowed to mpesa.sarafu.eth." msgstr "Ni sarafu za USD pekee zinazoruhusiwa kwa mpesa.sarafu.eth." + +msgid "Maximum amount: %s %s\nEnter amount:" +msgstr "Kiwango cha juu: %s %s\nWeka kiwango:" + +msgid "Credit Available: %s %s\n(You can swap up to %s %s -> %s %s).\nEnter %s amount:" +msgstr "Kiwango kinachopatikana: %s %s\n(Unaweza kubadilisha hadi %s %s -> %s %s)\nWeka %s kiwango:" + From de539dc3007c859dc5b2e1b35ef84e7bf1b11a69 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Tue, 28 Oct 2025 17:15:39 +0300 Subject: [PATCH 29/44] update the credit-send functionality to display the RAT data --- handlers/application/send.go | 106 ++++++++++++++++++++++------------- 1 file changed, 66 insertions(+), 40 deletions(-) diff --git a/handlers/application/send.go b/handlers/application/send.go index 7d47115..9c054dd 100644 --- a/handlers/application/send.go +++ b/handlers/application/send.go @@ -294,13 +294,14 @@ func (h *MenuHandlers) ResetTransactionAmount(ctx context.Context, sym string, i } flag_invalid_amount, _ := h.flagManager.GetFlag("flag_invalid_amount") + flag_swap_transaction, _ := h.flagManager.GetFlag("flag_swap_transaction") store := h.userdataStore err = store.WriteEntry(ctx, sessionId, storedb.DATA_AMOUNT, []byte("")) if err != nil { return res, nil } - res.FlagReset = append(res.FlagReset, flag_invalid_amount) + res.FlagReset = append(res.FlagReset, flag_invalid_amount, flag_swap_transaction) return res, nil } @@ -321,6 +322,10 @@ func (h *MenuHandlers) MaxAmount(ctx context.Context, sym string, input []byte) flag_swap_transaction, _ := h.flagManager.GetFlag("flag_swap_transaction") userStore := h.userdataStore + code := codeFromCtx(ctx) + l := gotext.NewLocale(translationDir, code) + l.AddDomain("default") + // Fetch session data transactionType, activeBal, activeSym, activeAddress, publicKey, activeDecimal, err := h.getSessionData(ctx, sessionId) if err != nil { @@ -333,7 +338,8 @@ func (h *MenuHandlers) MaxAmount(ctx context.Context, sym string, input []byte) // If normal transaction, return balance if string(transactionType) == "normal" { res.FlagReset = append(res.FlagReset, flag_swap_transaction) - res.Content = fmt.Sprintf("%s %s", formattedBalance, string(activeSym)) + + res.Content = l.Get("Maximum amount: %s %s\nEnter amount:", formattedBalance, string(activeSym)) return res, nil } @@ -363,37 +369,38 @@ func (h *MenuHandlers) MaxAmount(ctx context.Context, sym string, input []byte) res.FlagSet = append(res.FlagSet, flag_api_error) logg.ErrorCtxf(ctx, "failed on CheckTokenInPool", "error", err) } - res.Content = fmt.Sprintf("%s %s", formattedBalance, string(activeSym)) - return res, err + res.FlagReset = append(res.FlagReset, flag_swap_transaction) + res.Content = l.Get("Maximum amount: %s %s\nEnter amount:", formattedBalance, string(activeSym)) + return res, nil } - // Calculate max swappable amount - maxStr, err := h.calculateSwapMaxAmount(ctx, activePoolAddress, activeAddress, recipientActiveAddress, publicKey, activeDecimal) + // retrieve the max credit send amounts + maxSAT, maxRAT, err := h.calculateSendCreditLimits(ctx, activePoolAddress, activeAddress, recipientActiveAddress, publicKey, activeDecimal, recipientActiveDecimal) if err != nil { res.FlagSet = append(res.FlagSet, flag_api_error) return res, err } // Fallback if below minimum - maxFloat, _ := strconv.ParseFloat(maxStr, 64) + maxFloat, _ := strconv.ParseFloat(maxSAT, 64) if maxFloat < 0.1 { - res.Content = fmt.Sprintf("%s %s", formattedBalance, string(activeSym)) + res.FlagReset = append(res.FlagReset, flag_swap_transaction) + res.Content = l.Get("Maximum amount: %s %s\nEnter amount:", formattedBalance, string(activeSym)) return res, nil } - // Save max swap amount and return - err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SWAP_MAX_AMOUNT, []byte(maxStr)) + // Save max RAT amount to be used in validating the user's input + err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SWAP_MAX_AMOUNT, []byte(maxRAT)) if err != nil { - logg.ErrorCtxf(ctx, "failed to write swap max amount", "value", maxStr, "error", err) + logg.ErrorCtxf(ctx, "failed to write swap max amount (maxRAT)", "value", maxRAT, "error", err) return res, err } // save swap related data for the swap preview metadata := &dataserviceapi.TokenHoldings{ - TokenSymbol: string(recipientActiveSym), - Balance: formattedBalance, //not used - TokenDecimals: string(recipientActiveDecimal), TokenAddress: string(recipientActiveAddress), + TokenSymbol: string(recipientActiveSym), + TokenDecimals: string(recipientActiveDecimal), } // Store the active swap_to data @@ -402,7 +409,17 @@ func (h *MenuHandlers) MaxAmount(ctx context.Context, sym string, input []byte) return res, err } - res.Content = fmt.Sprintf("%s %s", maxStr, string(activeSym)) + res.Content = l.Get( + "Credit Available: %s %s\n(You can swap up to %s %s -> %s %s).\nEnter %s amount:", + maxRAT, + string(recipientActiveSym), + maxSAT, + string(activeSym), + maxRAT, + string(recipientActiveSym), + string(recipientActiveSym), + ) + return res, nil } @@ -472,8 +489,8 @@ func (h *MenuHandlers) resolveActivePoolAddress(ctx context.Context, sessionId s return nil, err } -func (h *MenuHandlers) calculateSwapMaxAmount(ctx context.Context, poolAddress, fromAddress, toAddress, publicKey, decimal []byte) (string, error) { - swapLimit, err := h.accountService.GetSwapFromTokenMaxLimit( +func (h *MenuHandlers) calculateSendCreditLimits(ctx context.Context, poolAddress, fromAddress, toAddress, publicKey, fromDecimal, toDecimal []byte) (string, string, error) { + creditSendMaxLimits, err := h.accountService.GetCreditSendMaxLimit( ctx, string(poolAddress), string(fromAddress), @@ -481,14 +498,17 @@ func (h *MenuHandlers) calculateSwapMaxAmount(ctx context.Context, poolAddress, string(publicKey), ) if err != nil { - logg.ErrorCtxf(ctx, "failed on GetSwapFromTokenMaxLimit", "error", err) - return "", err + logg.ErrorCtxf(ctx, "failed on GetCreditSendMaxLimit", "error", err) + return "", "", err } - scaled := store.ScaleDownBalance(swapLimit.Max, string(decimal)) + scaledSAT := store.ScaleDownBalance(creditSendMaxLimits.MaxSAT, string(fromDecimal)) + formattedSAT, _ := store.TruncateDecimalString(string(scaledSAT), 2) - formattedAmount, _ := store.TruncateDecimalString(string(scaled), 2) - return formattedAmount, nil + scaledRAT := store.ScaleDownBalance(creditSendMaxLimits.MaxRAT, string(toDecimal)) + formattedRAT, _ := store.TruncateDecimalString(string(scaledRAT), 2) + + return formattedSAT, formattedRAT, nil } // ValidateAmount ensures that the given input is a valid amount and that @@ -584,7 +604,7 @@ func (h *MenuHandlers) GetSender(ctx context.Context, sym string, input []byte) return res, nil } -// GetAmount retrieves the amount from teh Gdbm Db. +// GetAmount retrieves the transaction amount from the store. func (h *MenuHandlers) GetAmount(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result @@ -677,6 +697,7 @@ func (h *MenuHandlers) TransactionSwapPreview(ctx context.Context, sym string, i return res, fmt.Errorf("missing session") } + // Input in RAT inputStr := string(input) if inputStr == "0" { return res, nil @@ -701,14 +722,15 @@ func (h *MenuHandlers) TransactionSwapPreview(ctx context.Context, sym string, i return res, err } - maxValue, err := strconv.ParseFloat(swapData.ActiveSwapMaxAmount, 64) + // use the stored max RAT + maxRATValue, 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 { + if err != nil || inputAmount > maxRATValue { res.FlagSet = append(res.FlagSet, flag_invalid_amount) res.Content = inputStr return res, nil @@ -722,36 +744,33 @@ func (h *MenuHandlers) TransactionSwapPreview(ctx context.Context, sym string, i return res, nil } - finalAmountStr, err := store.ParseAndScaleAmount(formattedAmount, swapData.ActiveSwapFromDecimal) + finalAmountStr, err := store.ParseAndScaleAmount(formattedAmount, swapData.ActiveSwapToDecimal) 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) + // call the credit send API to get the reverse quote + r, err := h.accountService.GetCreditSendReverseQuote(ctx, swapData.ActivePoolAddress, swapData.ActiveSwapFromAddress, swapData.ActiveSwapToAddress, finalAmountStr) 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) + logg.ErrorCtxf(ctx, "failed GetCreditSendReverseQuote poolSwap", "error", err) return res, nil } - // store the outvalue as the final amount - err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_AMOUNT, []byte(r.OutValue)) + sendInputAmount := r.InputAmount // amount of SAT that should be swapped + sendOutputAmount := r.OutputAmount // amount of RAT that will be received + + // store the sendOutputAmount as the final amount (that will be sent) + err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_AMOUNT, []byte(sendOutputAmount)) if err != nil { - logg.ErrorCtxf(ctx, "failed to write amount value entry with", "key", storedb.DATA_AMOUNT, "value", r.OutValue, "error", err) + logg.ErrorCtxf(ctx, "failed to write output amount value entry with", "key", storedb.DATA_AMOUNT, "value", sendOutputAmount, "error", err) return res, err } - // Scale down the quoted amount - quoteAmountStr := store.ScaleDownBalance(r.OutValue, swapData.ActiveSwapToDecimal) + // Scale down the quoted output amount + quoteAmountStr := store.ScaleDownBalance(sendOutputAmount, swapData.ActiveSwapToDecimal) // Format the qouteAmount amount to 2 decimal places qouteAmount, _ := store.TruncateDecimalString(quoteAmountStr, 2) @@ -763,6 +782,13 @@ func (h *MenuHandlers) TransactionSwapPreview(ctx context.Context, sym string, i return res, err } + // store the sendInputAmount as the swap amount + err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SWAP_AMOUNT, []byte(sendInputAmount)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write swap amount entry with", "key", storedb.DATA_ACTIVE_SWAP_AMOUNT, "value", sendInputAmount, "error", err) + return res, err + } + res.Content = fmt.Sprintf( "%s will receive %s %s", string(recipientPhoneNumber), qouteAmount, swapData.ActiveSwapToSym, From 41f08c5c9b1ccee2657e0653348edb6d85616aa0 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Tue, 28 Oct 2025 17:16:25 +0300 Subject: [PATCH 30/44] display the max_amount directly from the function --- services/registration/amount | 3 +-- services/registration/amount_swa | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/services/registration/amount b/services/registration/amount index 9142aba..b50e87a 100644 --- a/services/registration/amount +++ b/services/registration/amount @@ -1,2 +1 @@ -Maximum amount: {{.max_amount}} -Enter amount: \ No newline at end of file +{{.max_amount}} \ No newline at end of file diff --git a/services/registration/amount_swa b/services/registration/amount_swa index 0c8cf01..b50e87a 100644 --- a/services/registration/amount_swa +++ b/services/registration/amount_swa @@ -1,2 +1 @@ -Kiwango cha juu: {{.max_amount}} -Weka kiwango: \ No newline at end of file +{{.max_amount}} \ No newline at end of file From 3194508e513fbca94ee4936a5222569ab8b42a2c Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Tue, 28 Oct 2025 17:16:54 +0300 Subject: [PATCH 31/44] increase the output sizes --- services/registration/amount.vis | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/registration/amount.vis b/services/registration/amount.vis index e576600..a1b9a86 100644 --- a/services/registration/amount.vis +++ b/services/registration/amount.vis @@ -1,5 +1,5 @@ -LOAD reset_transaction_amount 0 -LOAD max_amount 40 +LOAD reset_transaction_amount 10 +LOAD max_amount 160 RELOAD max_amount MAP max_amount MOUT back 0 From 9da2f8a6ac9873f8d8be2b95393bbb5ce41b09e9 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Tue, 28 Oct 2025 17:17:22 +0300 Subject: [PATCH 32/44] have a node for invalid credit send amounts --- services/registration/invalid_credit_send_amount | 1 + services/registration/invalid_credit_send_amount.vis | 7 +++++++ services/registration/invalid_credit_send_amount_swa | 1 + 3 files changed, 9 insertions(+) create mode 100644 services/registration/invalid_credit_send_amount create mode 100644 services/registration/invalid_credit_send_amount.vis create mode 100644 services/registration/invalid_credit_send_amount_swa diff --git a/services/registration/invalid_credit_send_amount b/services/registration/invalid_credit_send_amount new file mode 100644 index 0000000..afe2d5d --- /dev/null +++ b/services/registration/invalid_credit_send_amount @@ -0,0 +1 @@ +Amount {{.transaction_swap_preview}} is invalid, please try again: \ No newline at end of file diff --git a/services/registration/invalid_credit_send_amount.vis b/services/registration/invalid_credit_send_amount.vis new file mode 100644 index 0000000..7870d57 --- /dev/null +++ b/services/registration/invalid_credit_send_amount.vis @@ -0,0 +1,7 @@ +MAP transaction_swap_preview +RELOAD reset_transaction_amount +MOUT retry 1 +MOUT quit 9 +HALT +INCMP ^ 1 +INCMP quit 9 diff --git a/services/registration/invalid_credit_send_amount_swa b/services/registration/invalid_credit_send_amount_swa new file mode 100644 index 0000000..4e0d8b8 --- /dev/null +++ b/services/registration/invalid_credit_send_amount_swa @@ -0,0 +1 @@ +Kiwango {{.transaction_swap_preview}} sio sahihi, tafadhali weka tena: \ No newline at end of file From 95b48371cebf02199d3970e432b173bd1dd0b3a2 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Tue, 28 Oct 2025 17:19:40 +0300 Subject: [PATCH 33/44] CATCH invalid credit-swap amounts --- services/registration/transaction_swap.vis | 1 + 1 file changed, 1 insertion(+) diff --git a/services/registration/transaction_swap.vis b/services/registration/transaction_swap.vis index 7f2f53e..25e802c 100644 --- a/services/registration/transaction_swap.vis +++ b/services/registration/transaction_swap.vis @@ -1,6 +1,7 @@ LOAD transaction_swap_preview 0 MAP transaction_swap_preview CATCH api_failure flag_api_call_error 1 +CATCH invalid_credit_send_amount flag_invalid_amount 1 MOUT back 0 MOUT quit 9 LOAD authorize_account 6 From a49257657ed70ef3770d6801f1042e76164e51d2 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Tue, 28 Oct 2025 17:20:31 +0300 Subject: [PATCH 34/44] switch the order to match the TokenHoldings struct --- store/swap.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/store/swap.go b/store/swap.go index a63a33c..3510720 100644 --- a/store/swap.go +++ b/store/swap.go @@ -173,9 +173,9 @@ func UpdateSwapToVoucherData(ctx context.Context, store DataStore, sessionId str logg.InfoCtxf(ctx, "UpdateSwapToVoucherData", "data", data) // Active swap to voucher data entries activeEntries := map[storedb.DataTyp][]byte{ + storedb.DATA_ACTIVE_SWAP_TO_ADDRESS: []byte(data.TokenAddress), 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.TokenAddress), } // Write active data From 38ab1ecdd195446f1b7480d9251da3670c8c7fad Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Wed, 29 Oct 2025 10:53:11 +0300 Subject: [PATCH 35/44] added an edge-case test for precision --- store/tokens_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/store/tokens_test.go b/store/tokens_test.go index 625a65d..34cca1e 100644 --- a/store/tokens_test.go +++ b/store/tokens_test.go @@ -22,6 +22,13 @@ func TestTruncateDecimalString(t *testing.T) { want: "4.00", expectError: false, }, + { + name: "precision test", + input: "2.1", + decimalPlaces: 2, + want: "2.10", + expectError: false, + }, { name: "single decimal", input: "4.1", From 4c80606b56d0c1b121d7091134716a711026fd9a Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Wed, 29 Oct 2025 10:55:42 +0300 Subject: [PATCH 36/44] properly handle formatting, preventing rounding errors for the case of 2.1 -> 2.09 --- store/tokens.go | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/store/tokens.go b/store/tokens.go index 49ac175..0ac7bdf 100644 --- a/store/tokens.go +++ b/store/tokens.go @@ -7,6 +7,7 @@ import ( "math/big" "reflect" "strconv" + "strings" storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" ) @@ -21,25 +22,34 @@ type TransactionData struct { ActiveAddress string } -// TruncateDecimalString safely truncates the input amount to the specified decimal places +// TruncateDecimalString safely truncates (not rounds) a number string to the specified decimal places func TruncateDecimalString(input string, decimalPlaces int) (string, error) { - num, ok := new(big.Float).SetString(input) - if !ok { + if _, err := strconv.ParseFloat(input, 64); err != nil { return "", fmt.Errorf("invalid input") } - // Multiply by 10^decimalPlaces - scale := new(big.Float).SetInt(new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(decimalPlaces)), nil)) - scaled := new(big.Float).Mul(num, scale) + // Split input into integer and fractional parts + parts := strings.SplitN(input, ".", 2) + intPart := parts[0] + var fracPart string - // Truncate by converting to int (chops off decimals) - intPart, _ := scaled.Int(nil) + if len(parts) == 2 { + fracPart = parts[1] + } - // Divide back to get truncated float - truncated := new(big.Float).Quo(new(big.Float).SetInt(intPart), scale) + // Truncate or pad fractional part + if len(fracPart) > decimalPlaces { + fracPart = fracPart[:decimalPlaces] + } else { + fracPart = fracPart + strings.Repeat("0", decimalPlaces-len(fracPart)) + } - // Format with fixed decimals - return truncated.Text('f', decimalPlaces), nil + // Handle zero decimal places + if decimalPlaces == 0 { + return intPart, nil + } + + return fmt.Sprintf("%s.%s", intPart, fracPart), nil } func ParseAndScaleAmount(storedAmount, activeDecimal string) (string, error) { From d2b934fedac0f51cd7b1a00eca68a7a3734688a7 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Thu, 30 Oct 2025 10:07:05 +0300 Subject: [PATCH 37/44] use the same translation from the english menu --- services/registration/send_swa | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/registration/send_swa b/services/registration/send_swa index 016760e..5deea97 100644 --- a/services/registration/send_swa +++ b/services/registration/send_swa @@ -1 +1 @@ -Weka nambari ya simu: \ No newline at end of file +Weka nambari ya simu/Anwani/Lakabu: \ No newline at end of file From 878b5d0aa5c1f6bac2c0f26ae7ed164af3c6635e Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Thu, 30 Oct 2025 11:08:18 +0300 Subject: [PATCH 38/44] added the credit_send top-level menu --- services/registration/credit_send | 1 + services/registration/credit_send.vis | 12 ++++++++++++ services/registration/credit_send_menu | 1 + services/registration/credit_send_menu_swa | 1 + services/registration/credit_send_swa | 1 + services/registration/main.vis | 22 ++++++++++++---------- 6 files changed, 28 insertions(+), 10 deletions(-) create mode 100644 services/registration/credit_send create mode 100644 services/registration/credit_send.vis create mode 100644 services/registration/credit_send_menu create mode 100644 services/registration/credit_send_menu_swa create mode 100644 services/registration/credit_send_swa diff --git a/services/registration/credit_send b/services/registration/credit_send new file mode 100644 index 0000000..306466c --- /dev/null +++ b/services/registration/credit_send @@ -0,0 +1 @@ +Enter recipient's phone number/address/alias: \ No newline at end of file diff --git a/services/registration/credit_send.vis b/services/registration/credit_send.vis new file mode 100644 index 0000000..35db249 --- /dev/null +++ b/services/registration/credit_send.vis @@ -0,0 +1,12 @@ +LOAD transaction_reset 0 +RELOAD transaction_reset +CATCH no_voucher flag_no_active_voucher 1 +MOUT back 0 +HALT +LOAD validate_recipient 50 +RELOAD validate_recipient +CATCH api_failure flag_api_call_error 1 +CATCH invalid_recipient flag_invalid_recipient 1 +CATCH invite_recipient flag_invalid_recipient_with_invite 1 +INCMP _ 0 +INCMP credit_amount * diff --git a/services/registration/credit_send_menu b/services/registration/credit_send_menu new file mode 100644 index 0000000..4362df7 --- /dev/null +++ b/services/registration/credit_send_menu @@ -0,0 +1 @@ +Credit-Send \ No newline at end of file diff --git a/services/registration/credit_send_menu_swa b/services/registration/credit_send_menu_swa new file mode 100644 index 0000000..2d8f3c2 --- /dev/null +++ b/services/registration/credit_send_menu_swa @@ -0,0 +1 @@ +Tuma-Mkopo \ No newline at end of file diff --git a/services/registration/credit_send_swa b/services/registration/credit_send_swa new file mode 100644 index 0000000..a44919b --- /dev/null +++ b/services/registration/credit_send_swa @@ -0,0 +1 @@ +Weka nambari ya simu/anwani/lakabu: \ No newline at end of file diff --git a/services/registration/main.vis b/services/registration/main.vis index c48a43d..725aa74 100644 --- a/services/registration/main.vis +++ b/services/registration/main.vis @@ -7,18 +7,20 @@ LOAD check_balance 128 RELOAD check_balance MAP check_balance MOUT send 1 -MOUT swap 2 -MOUT vouchers 3 -MOUT select_pool 4 -MOUT account 5 -MOUT help 6 +MOUT credit_send 2 +MOUT swap 3 +MOUT vouchers 4 +MOUT select_pool 5 +MOUT account 6 +MOUT help 7 MOUT quit 9 HALT INCMP send 1 -INCMP swap_to_list 2 -INCMP my_vouchers 3 -INCMP select_pool 4 -INCMP my_account 5 -INCMP help 6 +INCMP credit_send 2 +INCMP swap_to_list 3 +INCMP my_vouchers 4 +INCMP select_pool 5 +INCMP my_account 6 +INCMP help 7 INCMP quit 9 INCMP . * From 37f4b6067914ec798c1cf98149ca2339a0e8ae32 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Thu, 30 Oct 2025 11:08:57 +0300 Subject: [PATCH 39/44] removed the credit-swap related code --- services/registration/amount.vis | 3 --- 1 file changed, 3 deletions(-) diff --git a/services/registration/amount.vis b/services/registration/amount.vis index a1b9a86..83094b6 100644 --- a/services/registration/amount.vis +++ b/services/registration/amount.vis @@ -4,9 +4,6 @@ RELOAD max_amount MAP max_amount MOUT back 0 HALT -LOAD clear_trans_type_flag 6 -RELOAD clear_trans_type_flag -CATCH transaction_swap flag_swap_transaction 1 LOAD validate_amount 64 RELOAD validate_amount CATCH api_failure flag_api_call_error 1 From 4092437d215258455331fa9d64318e1bea798066 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Thu, 30 Oct 2025 11:11:12 +0300 Subject: [PATCH 40/44] add a node for the credit-send amount --- services/registration/credit_amount | 1 + services/registration/credit_amount.vis | 18 ++++++++++++++++++ services/registration/credit_amount_swa | 1 + 3 files changed, 20 insertions(+) create mode 100644 services/registration/credit_amount create mode 100644 services/registration/credit_amount.vis create mode 100644 services/registration/credit_amount_swa diff --git a/services/registration/credit_amount b/services/registration/credit_amount new file mode 100644 index 0000000..5683878 --- /dev/null +++ b/services/registration/credit_amount @@ -0,0 +1 @@ +{{.credit_max_amount}} \ No newline at end of file diff --git a/services/registration/credit_amount.vis b/services/registration/credit_amount.vis new file mode 100644 index 0000000..f6b3055 --- /dev/null +++ b/services/registration/credit_amount.vis @@ -0,0 +1,18 @@ +LOAD reset_transaction_amount 10 +LOAD credit_max_amount 160 +RELOAD credit_max_amount +MAP credit_max_amount +MOUT back 0 +HALT +LOAD clear_trans_type_flag 6 +RELOAD clear_trans_type_flag +CATCH transaction_swap flag_swap_transaction 1 +LOAD validate_amount 64 +RELOAD validate_amount +CATCH api_failure flag_api_call_error 1 +CATCH invalid_amount flag_invalid_amount 1 +INCMP _ 0 +LOAD get_recipient 0 +LOAD get_sender 64 +LOAD get_amount 32 +INCMP transaction_pin * diff --git a/services/registration/credit_amount_swa b/services/registration/credit_amount_swa new file mode 100644 index 0000000..5683878 --- /dev/null +++ b/services/registration/credit_amount_swa @@ -0,0 +1 @@ +{{.credit_max_amount}} \ No newline at end of file From 8ce17a8d1e841cee9b7ace9a1d8583a54215ce27 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Thu, 30 Oct 2025 11:11:39 +0300 Subject: [PATCH 41/44] add a sym for the credit_max_amount --- handlers/local.go | 1 + 1 file changed, 1 insertion(+) diff --git a/handlers/local.go b/handlers/local.go index 836db72..0319a47 100644 --- a/handlers/local.go +++ b/handlers/local.go @@ -86,6 +86,7 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountService) ls.DbRs.AddLocalFunc("transaction_reset", appHandlers.TransactionReset) ls.DbRs.AddLocalFunc("invite_valid_recipient", appHandlers.InviteValidRecipient) ls.DbRs.AddLocalFunc("max_amount", appHandlers.MaxAmount) + ls.DbRs.AddLocalFunc("credit_max_amount", appHandlers.MaxAmount) ls.DbRs.AddLocalFunc("validate_amount", appHandlers.ValidateAmount) ls.DbRs.AddLocalFunc("reset_transaction_amount", appHandlers.ResetTransactionAmount) ls.DbRs.AddLocalFunc("get_recipient", appHandlers.GetRecipient) From 582f349be39af1b97e6134223645a8ace7a6f715 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Thu, 30 Oct 2025 11:12:16 +0300 Subject: [PATCH 42/44] handle normal send transactions based on the sym --- handlers/application/send.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/handlers/application/send.go b/handlers/application/send.go index 9c054dd..cd93102 100644 --- a/handlers/application/send.go +++ b/handlers/application/send.go @@ -335,8 +335,8 @@ func (h *MenuHandlers) MaxAmount(ctx context.Context, sym string, input []byte) // Format the active balance amount to 2 decimal places formattedBalance, _ := store.TruncateDecimalString(string(activeBal), 2) - // If normal transaction, return balance - if string(transactionType) == "normal" { + // If normal transaction, or if the sym is max_amount, return balance + if string(transactionType) == "normal" || sym == "max_amount" { res.FlagReset = append(res.FlagReset, flag_swap_transaction) res.Content = l.Get("Maximum amount: %s %s\nEnter amount:", formattedBalance, string(activeSym)) From f51f577e2a79393cc4080db57173059ccab618db Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Thu, 30 Oct 2025 12:35:46 +0300 Subject: [PATCH 43/44] added the swahili translations --- services/registration/locale/swa/default.po | 4 +++- services/registration/transaction_swap_swa | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 services/registration/transaction_swap_swa diff --git a/services/registration/locale/swa/default.po b/services/registration/locale/swa/default.po index 18d6097..96bfc07 100644 --- a/services/registration/locale/swa/default.po +++ b/services/registration/locale/swa/default.po @@ -50,5 +50,7 @@ msgid "Maximum amount: %s %s\nEnter amount:" msgstr "Kiwango cha juu: %s %s\nWeka kiwango:" msgid "Credit Available: %s %s\n(You can swap up to %s %s -> %s %s).\nEnter %s amount:" -msgstr "Kiwango kinachopatikana: %s %s\n(Unaweza kubadilisha hadi %s %s -> %s %s)\nWeka %s kiwango:" +msgstr "Kiwango kinachopatikana: %s %s\n(Unaweza kubadilisha hadi %s %s -> %s %s)\nWeka kiwango cha %s:" +msgid "%s will receive %s %s" +msgstr "%s atapokea %s %s" \ No newline at end of file diff --git a/services/registration/transaction_swap_swa b/services/registration/transaction_swap_swa new file mode 100644 index 0000000..9d2df58 --- /dev/null +++ b/services/registration/transaction_swap_swa @@ -0,0 +1,3 @@ +{{.transaction_swap_preview}} + +Tafadhali weka PIN yako kudhibitisha: \ No newline at end of file From 348185ef96c549a6ac94f160156453e08ebb35f5 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Thu, 30 Oct 2025 12:36:11 +0300 Subject: [PATCH 44/44] use l.Get for the language change --- handlers/application/send.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/application/send.go b/handlers/application/send.go index cd93102..70c126d 100644 --- a/handlers/application/send.go +++ b/handlers/application/send.go @@ -789,7 +789,7 @@ func (h *MenuHandlers) TransactionSwapPreview(ctx context.Context, sym string, i return res, err } - res.Content = fmt.Sprintf( + res.Content = l.Get( "%s will receive %s %s", string(recipientPhoneNumber), qouteAmount, swapData.ActiveSwapToSym, )