Compare commits

..

25 Commits

Author SHA1 Message Date
fc2ca0f546 Merge pull request 'send with swap' (#102) from send-with-swap into master
Some checks failed
release / docker (push) Has been cancelled
Reviewed-on: #102
2025-11-12 08:54:05 +01:00
724bc1bcf3 Merge branch 'master' into send-with-swap
Some checks failed
release / docker (push) Has been cancelled
2025-11-05 15:32:35 +03:00
6e8c0fbcb3 Merge pull request 'Fix get_pools being skipped' (#108) from pool-debug into master
Some checks failed
release / docker (push) Has been cancelled
Reviewed-on: #108
2025-11-05 12:12:14 +01:00
9f8cf95e0f
remove print debug statements 2025-11-05 11:20:55 +03:00
97be6e943c
add a reload for the get_pools 2025-11-05 11:16:52 +03:00
ba93bd9152
add debug logs for the pool data entry 2025-11-05 11:03:45 +03:00
5fac27d00e
add debug logs for the pool keys 2025-11-04 15:27:15 +03:00
348185ef96
use l.Get for the language change
Some checks failed
release / docker (push) Has been cancelled
2025-10-30 12:36:11 +03:00
f51f577e2a
added the swahili translations 2025-10-30 12:35:46 +03:00
582f349be3
handle normal send transactions based on the sym 2025-10-30 11:12:16 +03:00
8ce17a8d1e
add a sym for the credit_max_amount 2025-10-30 11:11:39 +03:00
4092437d21
add a node for the credit-send amount 2025-10-30 11:11:12 +03:00
37f4b60679
removed the credit-swap related code 2025-10-30 11:08:57 +03:00
878b5d0aa5
added the credit_send top-level menu 2025-10-30 11:08:18 +03:00
d2b934feda
use the same translation from the english menu 2025-10-30 10:07:05 +03:00
4c80606b56
properly handle formatting, preventing rounding errors for the case of 2.1 -> 2.09
Some checks failed
release / docker (push) Has been cancelled
2025-10-29 10:55:42 +03:00
38ab1ecdd1
added an edge-case test for precision 2025-10-29 10:53:11 +03:00
a49257657e
switch the order to match the TokenHoldings struct
Some checks failed
release / docker (push) Has been cancelled
2025-10-28 17:20:31 +03:00
95b48371ce
CATCH invalid credit-swap amounts 2025-10-28 17:19:40 +03:00
9da2f8a6ac
have a node for invalid credit send amounts 2025-10-28 17:17:22 +03:00
3194508e51
increase the output sizes 2025-10-28 17:16:54 +03:00
41f08c5c9b
display the max_amount directly from the function 2025-10-28 17:16:25 +03:00
de539dc300
update the credit-send functionality to display the RAT data 2025-10-28 17:15:39 +03:00
8af2ccd36f
added text translations for the amounts (for normal and credit-send transactions) 2025-10-28 17:09:46 +03:00
d9c49ee119
upgraded sarafu-api to make use of the credit-send API 2025-10-28 12:21:42 +03:00
27 changed files with 181 additions and 77 deletions

2
go.mod
View File

@ -5,7 +5,7 @@ go 1.23.4
require ( require (
git.defalsify.org/vise.git v0.3.2-0.20250528124150-03bf7bfc1b66 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/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 v0.9.0-beta.2.0.20250408094335-e2d1f65bb306
git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694 git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694
github.com/alecthomas/assert/v2 v2.2.2 github.com/alecthomas/assert/v2 v2.2.2

4
go.sum
View File

@ -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.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 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.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 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 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= git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694 h1:DjJlBSz0S13acft5XZDWk7ZYnzElym0xLMYEVgyNJ+E=

View File

@ -294,13 +294,14 @@ func (h *MenuHandlers) ResetTransactionAmount(ctx context.Context, sym string, i
} }
flag_invalid_amount, _ := h.flagManager.GetFlag("flag_invalid_amount") flag_invalid_amount, _ := h.flagManager.GetFlag("flag_invalid_amount")
flag_swap_transaction, _ := h.flagManager.GetFlag("flag_swap_transaction")
store := h.userdataStore store := h.userdataStore
err = store.WriteEntry(ctx, sessionId, storedb.DATA_AMOUNT, []byte("")) err = store.WriteEntry(ctx, sessionId, storedb.DATA_AMOUNT, []byte(""))
if err != nil { if err != nil {
return res, 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 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") flag_swap_transaction, _ := h.flagManager.GetFlag("flag_swap_transaction")
userStore := h.userdataStore userStore := h.userdataStore
code := codeFromCtx(ctx)
l := gotext.NewLocale(translationDir, code)
l.AddDomain("default")
// Fetch session data // Fetch session data
transactionType, activeBal, activeSym, activeAddress, publicKey, activeDecimal, err := h.getSessionData(ctx, sessionId) transactionType, activeBal, activeSym, activeAddress, publicKey, activeDecimal, err := h.getSessionData(ctx, sessionId)
if err != nil { if err != nil {
@ -330,10 +335,11 @@ func (h *MenuHandlers) MaxAmount(ctx context.Context, sym string, input []byte)
// Format the active balance amount to 2 decimal places // Format the active balance amount to 2 decimal places
formattedBalance, _ := store.TruncateDecimalString(string(activeBal), 2) formattedBalance, _ := store.TruncateDecimalString(string(activeBal), 2)
// If normal transaction, return balance // If normal transaction, or if the sym is max_amount, return balance
if string(transactionType) == "normal" { if string(transactionType) == "normal" || sym == "max_amount" {
res.FlagReset = append(res.FlagReset, flag_swap_transaction) 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 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) res.FlagSet = append(res.FlagSet, flag_api_error)
logg.ErrorCtxf(ctx, "failed on CheckTokenInPool", "error", err) logg.ErrorCtxf(ctx, "failed on CheckTokenInPool", "error", err)
} }
res.Content = fmt.Sprintf("%s %s", formattedBalance, string(activeSym)) res.FlagReset = append(res.FlagReset, flag_swap_transaction)
return res, err res.Content = l.Get("Maximum amount: %s %s\nEnter amount:", formattedBalance, string(activeSym))
return res, nil
} }
// Calculate max swappable amount // retrieve the max credit send amounts
maxStr, err := h.calculateSwapMaxAmount(ctx, activePoolAddress, activeAddress, recipientActiveAddress, publicKey, activeDecimal) maxSAT, maxRAT, err := h.calculateSendCreditLimits(ctx, activePoolAddress, activeAddress, recipientActiveAddress, publicKey, activeDecimal, recipientActiveDecimal)
if err != nil { if err != nil {
res.FlagSet = append(res.FlagSet, flag_api_error) res.FlagSet = append(res.FlagSet, flag_api_error)
return res, err return res, err
} }
// Fallback if below minimum // Fallback if below minimum
maxFloat, _ := strconv.ParseFloat(maxStr, 64) maxFloat, _ := strconv.ParseFloat(maxSAT, 64)
if maxFloat < 0.1 { 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 return res, nil
} }
// Save max swap amount and return // 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(maxStr)) err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SWAP_MAX_AMOUNT, []byte(maxRAT))
if err != nil { 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 return res, err
} }
// save swap related data for the swap preview // save swap related data for the swap preview
metadata := &dataserviceapi.TokenHoldings{ metadata := &dataserviceapi.TokenHoldings{
TokenSymbol: string(recipientActiveSym),
Balance: formattedBalance, //not used
TokenDecimals: string(recipientActiveDecimal),
TokenAddress: string(recipientActiveAddress), TokenAddress: string(recipientActiveAddress),
TokenSymbol: string(recipientActiveSym),
TokenDecimals: string(recipientActiveDecimal),
} }
// Store the active swap_to data // Store the active swap_to data
@ -402,7 +409,17 @@ func (h *MenuHandlers) MaxAmount(ctx context.Context, sym string, input []byte)
return res, err 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 return res, nil
} }
@ -472,8 +489,8 @@ func (h *MenuHandlers) resolveActivePoolAddress(ctx context.Context, sessionId s
return nil, err return nil, err
} }
func (h *MenuHandlers) calculateSwapMaxAmount(ctx context.Context, poolAddress, fromAddress, toAddress, publicKey, decimal []byte) (string, error) { func (h *MenuHandlers) calculateSendCreditLimits(ctx context.Context, poolAddress, fromAddress, toAddress, publicKey, fromDecimal, toDecimal []byte) (string, string, error) {
swapLimit, err := h.accountService.GetSwapFromTokenMaxLimit( creditSendMaxLimits, err := h.accountService.GetCreditSendMaxLimit(
ctx, ctx,
string(poolAddress), string(poolAddress),
string(fromAddress), string(fromAddress),
@ -481,14 +498,17 @@ func (h *MenuHandlers) calculateSwapMaxAmount(ctx context.Context, poolAddress,
string(publicKey), string(publicKey),
) )
if err != nil { if err != nil {
logg.ErrorCtxf(ctx, "failed on GetSwapFromTokenMaxLimit", "error", err) logg.ErrorCtxf(ctx, "failed on GetCreditSendMaxLimit", "error", err)
return "", 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) scaledRAT := store.ScaleDownBalance(creditSendMaxLimits.MaxRAT, string(toDecimal))
return formattedAmount, nil formattedRAT, _ := store.TruncateDecimalString(string(scaledRAT), 2)
return formattedSAT, formattedRAT, nil
} }
// ValidateAmount ensures that the given input is a valid amount and that // 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 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) { func (h *MenuHandlers) GetAmount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result var res resource.Result
@ -677,6 +697,7 @@ func (h *MenuHandlers) TransactionSwapPreview(ctx context.Context, sym string, i
return res, fmt.Errorf("missing session") return res, fmt.Errorf("missing session")
} }
// Input in RAT
inputStr := string(input) inputStr := string(input)
if inputStr == "0" { if inputStr == "0" {
return res, nil return res, nil
@ -701,14 +722,15 @@ func (h *MenuHandlers) TransactionSwapPreview(ctx context.Context, sym string, i
return res, err 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 { if err != nil {
logg.ErrorCtxf(ctx, "Failed to convert the swapMaxAmount to a float", "error", err) logg.ErrorCtxf(ctx, "Failed to convert the swapMaxAmount to a float", "error", err)
return res, err return res, err
} }
inputAmount, err := strconv.ParseFloat(inputStr, 64) 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.FlagSet = append(res.FlagSet, flag_invalid_amount)
res.Content = inputStr res.Content = inputStr
return res, nil return res, nil
@ -722,36 +744,33 @@ func (h *MenuHandlers) TransactionSwapPreview(ctx context.Context, sym string, i
return res, nil return res, nil
} }
finalAmountStr, err := store.ParseAndScaleAmount(formattedAmount, swapData.ActiveSwapFromDecimal) finalAmountStr, err := store.ParseAndScaleAmount(formattedAmount, swapData.ActiveSwapToDecimal)
if err != nil { if err != nil {
return res, err return res, err
} }
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SWAP_AMOUNT, []byte(finalAmountStr)) // call the credit send API to get the reverse quote
if err != nil { r, err := h.accountService.GetCreditSendReverseQuote(ctx, swapData.ActivePoolAddress, swapData.ActiveSwapFromAddress, swapData.ActiveSwapToAddress, finalAmountStr)
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 { if err != nil {
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
res.FlagSet = append(res.FlagSet, flag_api_error) res.FlagSet = append(res.FlagSet, flag_api_error)
res.Content = l.Get("Your request failed. Please try again later.") 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 return res, nil
} }
// store the outvalue as the final amount sendInputAmount := r.InputAmount // amount of SAT that should be swapped
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_AMOUNT, []byte(r.OutValue)) 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 { 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 return res, err
} }
// Scale down the quoted amount // Scale down the quoted output amount
quoteAmountStr := store.ScaleDownBalance(r.OutValue, swapData.ActiveSwapToDecimal) quoteAmountStr := store.ScaleDownBalance(sendOutputAmount, swapData.ActiveSwapToDecimal)
// Format the qouteAmount amount to 2 decimal places // Format the qouteAmount amount to 2 decimal places
qouteAmount, _ := store.TruncateDecimalString(quoteAmountStr, 2) qouteAmount, _ := store.TruncateDecimalString(quoteAmountStr, 2)
@ -763,7 +782,14 @@ func (h *MenuHandlers) TransactionSwapPreview(ctx context.Context, sym string, i
return res, err return res, err
} }
res.Content = fmt.Sprintf( // 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 = l.Get(
"%s will receive %s %s", "%s will receive %s %s",
string(recipientPhoneNumber), qouteAmount, swapData.ActiveSwapToSym, string(recipientPhoneNumber), qouteAmount, swapData.ActiveSwapToSym,
) )

View File

@ -86,6 +86,7 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountService)
ls.DbRs.AddLocalFunc("transaction_reset", appHandlers.TransactionReset) ls.DbRs.AddLocalFunc("transaction_reset", appHandlers.TransactionReset)
ls.DbRs.AddLocalFunc("invite_valid_recipient", appHandlers.InviteValidRecipient) ls.DbRs.AddLocalFunc("invite_valid_recipient", appHandlers.InviteValidRecipient)
ls.DbRs.AddLocalFunc("max_amount", appHandlers.MaxAmount) 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("validate_amount", appHandlers.ValidateAmount)
ls.DbRs.AddLocalFunc("reset_transaction_amount", appHandlers.ResetTransactionAmount) ls.DbRs.AddLocalFunc("reset_transaction_amount", appHandlers.ResetTransactionAmount)
ls.DbRs.AddLocalFunc("get_recipient", appHandlers.GetRecipient) ls.DbRs.AddLocalFunc("get_recipient", appHandlers.GetRecipient)

View File

@ -1,2 +1 @@
Maximum amount: {{.max_amount}} {{.max_amount}}
Enter amount:

View File

@ -1,12 +1,9 @@
LOAD reset_transaction_amount 0 LOAD reset_transaction_amount 10
LOAD max_amount 40 LOAD max_amount 160
RELOAD max_amount RELOAD max_amount
MAP max_amount MAP max_amount
MOUT back 0 MOUT back 0
HALT HALT
LOAD clear_trans_type_flag 6
RELOAD clear_trans_type_flag
CATCH transaction_swap flag_swap_transaction 1
LOAD validate_amount 64 LOAD validate_amount 64
RELOAD validate_amount RELOAD validate_amount
CATCH api_failure flag_api_call_error 1 CATCH api_failure flag_api_call_error 1

View File

@ -1,2 +1 @@
Kiwango cha juu: {{.max_amount}} {{.max_amount}}
Weka kiwango:

View File

@ -0,0 +1 @@
{{.credit_max_amount}}

View File

@ -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 *

View File

@ -0,0 +1 @@
{{.credit_max_amount}}

View File

@ -0,0 +1 @@
Enter recipient's phone number/address/alias:

View File

@ -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 *

View File

@ -0,0 +1 @@
Credit-Send

View File

@ -0,0 +1 @@
Tuma-Mkopo

View File

@ -0,0 +1 @@
Weka nambari ya simu/anwani/lakabu:

View File

@ -0,0 +1 @@
Amount {{.transaction_swap_preview}} is invalid, please try again:

View File

@ -0,0 +1,7 @@
MAP transaction_swap_preview
RELOAD reset_transaction_amount
MOUT retry 1
MOUT quit 9
HALT
INCMP ^ 1
INCMP quit 9

View File

@ -0,0 +1 @@
Kiwango {{.transaction_swap_preview}} sio sahihi, tafadhali weka tena:

View File

@ -45,3 +45,12 @@ msgstr "Jina: %s\nSarafu: %s"
msgid "Only USD vouchers are allowed to mpesa.sarafu.eth." msgid "Only USD vouchers are allowed to mpesa.sarafu.eth."
msgstr "Ni sarafu za USD pekee zinazoruhusiwa kwa 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 kiwango cha %s:"
msgid "%s will receive %s %s"
msgstr "%s atapokea %s %s"

View File

@ -7,18 +7,20 @@ LOAD check_balance 128
RELOAD check_balance RELOAD check_balance
MAP check_balance MAP check_balance
MOUT send 1 MOUT send 1
MOUT swap 2 MOUT credit_send 2
MOUT vouchers 3 MOUT swap 3
MOUT select_pool 4 MOUT vouchers 4
MOUT account 5 MOUT select_pool 5
MOUT help 6 MOUT account 6
MOUT help 7
MOUT quit 9 MOUT quit 9
HALT HALT
INCMP send 1 INCMP send 1
INCMP swap_to_list 2 INCMP credit_send 2
INCMP my_vouchers 3 INCMP swap_to_list 3
INCMP select_pool 4 INCMP my_vouchers 4
INCMP my_account 5 INCMP select_pool 5
INCMP help 6 INCMP my_account 6
INCMP help 7
INCMP quit 9 INCMP quit 9
INCMP . * INCMP . *

View File

@ -1,5 +1,6 @@
CATCH no_voucher flag_no_active_voucher 1 CATCH no_voucher flag_no_active_voucher 1
LOAD get_pools 0 LOAD get_pools 0
RELOAD get_pools
MAP get_pools MAP get_pools
LOAD get_default_pool 20 LOAD get_default_pool 20
RELOAD get_default_pool RELOAD get_default_pool

View File

@ -1 +1 @@
Weka nambari ya simu: Weka nambari ya simu/Anwani/Lakabu:

View File

@ -1,6 +1,7 @@
LOAD transaction_swap_preview 0 LOAD transaction_swap_preview 0
MAP transaction_swap_preview MAP transaction_swap_preview
CATCH api_failure flag_api_call_error 1 CATCH api_failure flag_api_call_error 1
CATCH invalid_credit_send_amount flag_invalid_amount 1
MOUT back 0 MOUT back 0
MOUT quit 9 MOUT quit 9
LOAD authorize_account 6 LOAD authorize_account 6

View File

@ -0,0 +1,3 @@
{{.transaction_swap_preview}}
Tafadhali weka PIN yako kudhibitisha:

View File

@ -173,9 +173,9 @@ func UpdateSwapToVoucherData(ctx context.Context, store DataStore, sessionId str
logg.InfoCtxf(ctx, "UpdateSwapToVoucherData", "data", data) logg.InfoCtxf(ctx, "UpdateSwapToVoucherData", "data", data)
// Active swap to voucher data entries // Active swap to voucher data entries
activeEntries := map[storedb.DataTyp][]byte{ 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_SYM: []byte(data.TokenSymbol),
storedb.DATA_ACTIVE_SWAP_TO_DECIMAL: []byte(data.TokenDecimals), storedb.DATA_ACTIVE_SWAP_TO_DECIMAL: []byte(data.TokenDecimals),
storedb.DATA_ACTIVE_SWAP_TO_ADDRESS: []byte(data.TokenAddress),
} }
// Write active data // Write active data

View File

@ -7,6 +7,7 @@ import (
"math/big" "math/big"
"reflect" "reflect"
"strconv" "strconv"
"strings"
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
) )
@ -21,25 +22,34 @@ type TransactionData struct {
ActiveAddress string 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) { func TruncateDecimalString(input string, decimalPlaces int) (string, error) {
num, ok := new(big.Float).SetString(input) if _, err := strconv.ParseFloat(input, 64); err != nil {
if !ok {
return "", fmt.Errorf("invalid input") return "", fmt.Errorf("invalid input")
} }
// Multiply by 10^decimalPlaces // Split input into integer and fractional parts
scale := new(big.Float).SetInt(new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(decimalPlaces)), nil)) parts := strings.SplitN(input, ".", 2)
scaled := new(big.Float).Mul(num, scale) intPart := parts[0]
var fracPart string
// Truncate by converting to int (chops off decimals) if len(parts) == 2 {
intPart, _ := scaled.Int(nil) fracPart = parts[1]
}
// Divide back to get truncated float // Truncate or pad fractional part
truncated := new(big.Float).Quo(new(big.Float).SetInt(intPart), scale) if len(fracPart) > decimalPlaces {
fracPart = fracPart[:decimalPlaces]
} else {
fracPart = fracPart + strings.Repeat("0", decimalPlaces-len(fracPart))
}
// Format with fixed decimals // Handle zero decimal places
return truncated.Text('f', decimalPlaces), nil if decimalPlaces == 0 {
return intPart, nil
}
return fmt.Sprintf("%s.%s", intPart, fracPart), nil
} }
func ParseAndScaleAmount(storedAmount, activeDecimal string) (string, error) { func ParseAndScaleAmount(storedAmount, activeDecimal string) (string, error) {

View File

@ -22,6 +22,13 @@ func TestTruncateDecimalString(t *testing.T) {
want: "4.00", want: "4.00",
expectError: false, expectError: false,
}, },
{
name: "precision test",
input: "2.1",
decimalPlaces: 2,
want: "2.10",
expectError: false,
},
{ {
name: "single decimal", name: "single decimal",
input: "4.1", input: "4.1",