Compare commits

...

8 Commits

12 changed files with 93 additions and 48 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 {
@ -333,7 +338,8 @@ func (h *MenuHandlers) MaxAmount(ctx context.Context, sym string, input []byte)
// If normal transaction, return balance // If normal transaction, return balance
if string(transactionType) == "normal" { if string(transactionType) == "normal" {
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,6 +782,13 @@ func (h *MenuHandlers) TransactionSwapPreview(ctx context.Context, sym string, i
return res, err 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( res.Content = fmt.Sprintf(
"%s will receive %s %s", "%s will receive %s %s",
string(recipientPhoneNumber), qouteAmount, swapData.ActiveSwapToSym, string(recipientPhoneNumber), qouteAmount, swapData.ActiveSwapToSym,

View File

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

View File

@ -1,5 +1,5 @@
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

View File

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

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,10 @@ 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 %s kiwango:"

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

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