diff --git a/go.mod b/go.mod index d7a837f..0a488e4 100644 --- a/go.mod +++ b/go.mod @@ -5,12 +5,12 @@ go 1.23.4 require ( git.defalsify.org/vise.git v0.3.2-0.20250507172020-cb22240f1cb9 git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20250417111317-2953f4c2f32e - git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250428082711-5d221b8d565f + git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250517114512-050998ff82b1 git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306 git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694 github.com/alecthomas/assert/v2 v2.2.2 github.com/gofrs/uuid v4.4.0+incompatible - github.com/grassrootseconomics/ussd-data-service v1.2.0-beta + github.com/grassrootseconomics/ussd-data-service v1.4.4-beta github.com/jackc/pgx/v5 v5.7.1 github.com/peteole/testdata-loader v0.3.0 github.com/stretchr/testify v1.9.0 diff --git a/go.sum b/go.sum index 75de564..14da9e9 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,12 @@ git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20250417111317-2953 git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20250417111317-2953f4c2f32e/go.mod h1:wgQJZGIS6QuNLHqDhcsvehsbn5PvgV7aziRebMnJi60= git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250428082711-5d221b8d565f h1:OAHCP3YR1C5h1WFnnEnLs5kn6jTxQHQYWYtQaMZJIMY= git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250428082711-5d221b8d565f/go.mod h1:gOn89ipaDcDvmQXRMQYKUqcw/sJcwVOPVt2eC6Geip8= +git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250516094326-3b85167ad84a h1:QNh0NaKtGbSeRPlTVKEAnqc0R5rnVrpDMrCHD/EaU5U= +git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250516094326-3b85167ad84a/go.mod h1:K/TPgZ4OhPHBQq2X0ab3JZs4YjiexzSURZcfHLs9Pf4= +git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250517113706-ee434dba6980 h1:FQUwTDFWduY7gCMi1U2OiFlsuAHLojWUw2hvZ4cGC2s= +git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250517113706-ee434dba6980/go.mod h1:K/TPgZ4OhPHBQq2X0ab3JZs4YjiexzSURZcfHLs9Pf4= +git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250517114512-050998ff82b1 h1:0hvILlGkZnXO8o7nZth4xu77vAS4zVQ6Ae0rb5x/Idg= +git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250517114512-050998ff82b1/go.mod h1:K/TPgZ4OhPHBQq2X0ab3JZs4YjiexzSURZcfHLs9Pf4= git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306 h1:Jo+yWysWw/N5BJQtAyEMN8ePVvAyPHv+JG4lQti+1N4= git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306/go.mod h1:FdLwYtzsjOIcDiW4uDgDYnB4Wdzq12uJUe0QHSSPbSo= git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694 h1:DjJlBSz0S13acft5XZDWk7ZYnzElym0xLMYEVgyNJ+E= @@ -64,6 +70,10 @@ github.com/grassrootseconomics/ethutils v1.3.1 h1:LlQO90HjJkl7ejC8fv6jP7RJUrAm1j github.com/grassrootseconomics/ethutils v1.3.1/go.mod h1:Wuv1VEZrkLIXqTSEYI3Nh9HG/ZHOUQ+U+xvWJ8QtjgQ= github.com/grassrootseconomics/ussd-data-service v1.2.0-beta h1:fn1gwbWIwHVEBtUC2zi5OqTlfI/5gU1SMk0fgGixIXk= github.com/grassrootseconomics/ussd-data-service v1.2.0-beta/go.mod h1:omfI0QtUwIdpu9gMcUqLMCG8O1XWjqJGBx1qUMiGWC0= +github.com/grassrootseconomics/ussd-data-service v1.4.0-beta h1:4fMd/3h2ZIhRg4GdHQmRw5FfD3MpJvFNNJQo+Q27f5M= +github.com/grassrootseconomics/ussd-data-service v1.4.0-beta/go.mod h1:9sGnorpKaK76FmOGXoh/xv7x5siSFNYdXxQo9BKW4DI= +github.com/grassrootseconomics/ussd-data-service v1.4.4-beta h1:turlyo0i3OLj29mWpWNoB/3Qao8qEngT/5d1jDWTZE4= +github.com/grassrootseconomics/ussd-data-service v1.4.4-beta/go.mod h1:9sGnorpKaK76FmOGXoh/xv7x5siSFNYdXxQo9BKW4DI= github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 h1:U4kkNYryi/qfbBF8gh7Vsbuz+cVmhf5kt6pE9bYYyLo= github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4/go.mod h1:zpZDgZFzeq9s0MIeB1P50NIEWDFFHSFBohI/NbaTD/Y= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= diff --git a/handlers/application/menuhandler.go b/handlers/application/menuhandler.go index ba31717..40abbe5 100644 --- a/handlers/application/menuhandler.go +++ b/handlers/application/menuhandler.go @@ -2651,3 +2651,378 @@ func (h *MenuHandlers) ClearTemporaryValue(ctx context.Context, sym string, inpu } return res, nil } + +// GetPools fetches a list of 5 top pools +func (h *MenuHandlers) GetPools(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + sessionId, ok := ctx.Value("SessionId").(string) + if !ok { + return res, fmt.Errorf("missing session") + } + userStore := h.userdataStore + + flag_api_error, _ := h.flagManager.GetFlag("flag_api_error") + + // call the api to get a list of top 5 pools sorted by swaps + topPools, err := h.accountService.FetchTopPools(ctx) + if err != nil { + res.FlagSet = append(res.FlagSet, flag_api_error) + logg.ErrorCtxf(ctx, "failed on FetchTransactions", "error", err) + return res, err + } + + // Return if there are no pools + if len(topPools) == 0 { + return res, nil + } + + data := store.ProcessPools(topPools) + + // Store all Pool data + dataMap := map[storedb.DataTyp]string{ + storedb.DATA_POOL_NAMES: data.PoolNames, + storedb.DATA_POOL_SYMBOLS: data.PoolSymbols, + storedb.DATA_POOL_ADDRESSES: data.PoolContractAdrresses, + } + + // Write data entries + for key, value := range dataMap { + if err := userStore.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil { + logg.ErrorCtxf(ctx, "Failed to write data entry for sessionId: %s", sessionId, "key", key, "error", err) + continue + } + } + + res.Content = h.ReplaceSeparatorFunc(data.PoolSymbols) + + return res, nil +} + +// LoadSwapFromList returns a list of possible vouchers to swap to +func (h *MenuHandlers) LoadSwapToList(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + sessionId, ok := ctx.Value("SessionId").(string) + if !ok { + return res, fmt.Errorf("missing session") + } + + userStore := h.userdataStore + publicKey, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY) + if err != nil { + logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err) + return res, err + } + + // get the active address and symbol + activeAddress, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS) + if err != nil { + logg.ErrorCtxf(ctx, "failed to read activeAddress entry with", "key", storedb.DATA_ACTIVE_ADDRESS, "error", err) + return res, err + } + activeSym, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM) + if err != nil { + logg.ErrorCtxf(ctx, "failed to read activeSym entry with", "key", storedb.DATA_ACTIVE_SYM, "error", err) + return res, err + } + + code := codeFromCtx(ctx) + l := gotext.NewLocale(translationDir, code) + l.AddDomain("default") + + flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher") + flag_api_error, _ := h.flagManager.GetFlag("flag_api_error") + + inputStr := string(input) + if inputStr == "0" { + return res, nil + } + + defaultPool := dataserviceapi.PoolDetails{ + PoolName: "Kenya ROLA Pool", + PoolSymbol: "ROLA", + PoolContractAdrress: "0x48a953cA5cf5298bc6f6Af3C608351f537AAcb9e", + LimiterAddress: "", + VoucherRegistry: "", + } + + activePoolAddress := defaultPool.PoolContractAdrress + + // set the active pool contract address + err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_ADDRESS, []byte(activePoolAddress)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write active PoolContractAdrress entry with", "key", storedb.DATA_ACTIVE_POOL_ADDRESS, "value", activePoolAddress, "error", err) + return res, err + } + + // call the api using the ActivePoolAddress and ActiveVoucherAddress to check if it is part of the pool + r, err := h.accountService.CheckTokenInPool(ctx, activePoolAddress, string(activeAddress)) + if err != nil { + res.FlagSet = append(res.FlagSet, flag_api_error) + logg.ErrorCtxf(ctx, "failed on CheckTokenInPool", "error", err) + return res, err + } + + if !r.CanSwapFrom { + res.FlagSet = append(res.FlagSet, flag_incorrect_voucher) + res.Content = l.Get( + "%s is not in the KENYA ROLA POOL. Please update your voucher and try again.", + activeSym, + ) + return res, nil + } + + res.FlagReset = append(res.FlagReset, flag_incorrect_voucher) + + // call the api using the activePoolAddress and publicKey to get a list of SwapToSymbolsData + swapToList, err := h.accountService.GetPoolSwappableVouchers(ctx, string(activePoolAddress), string(publicKey)) + if err != nil { + res.FlagSet = append(res.FlagSet, flag_api_error) + logg.ErrorCtxf(ctx, "failed on FetchTransactions", "error", err) + return res, err + } + + // Return if there are no vouchers + if len(swapToList) == 0 { + return res, nil + } + + data := store.ProcessVouchers(swapToList) + + // Store all to list voucher data + dataMap := map[storedb.DataTyp]string{ + storedb.DATA_POOL_TO_SYMBOLS: data.Symbols, + storedb.DATA_POOL_TO_BALANCES: data.Balances, + storedb.DATA_POOL_TO_DECIMALS: data.Decimals, + storedb.DATA_POOL_TO_ADDRESSES: data.Addresses, + } + + for key, value := range dataMap { + if err := userStore.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil { + logg.ErrorCtxf(ctx, "Failed to write data entry for sessionId: %s", sessionId, "key", key, "error", err) + continue + } + } + + res.Content = h.ReplaceSeparatorFunc(data.Symbols) + + return res, nil +} + +// SwapMaxLimit returns the max FROM token +// check if max/tokenDecimals > 0.1 for UX purposes and to prevent swapping of dust values +func (h *MenuHandlers) SwapMaxLimit(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + sessionId, ok := ctx.Value("SessionId").(string) + if !ok { + return res, fmt.Errorf("missing session") + } + + flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher") + flag_api_error, _ := h.flagManager.GetFlag("flag_api_error") + flag_low_swap_amount, _ := h.flagManager.GetFlag("flag_low_swap_amount") + + res.FlagReset = append(res.FlagReset, flag_incorrect_voucher) + res.FlagReset = append(res.FlagSet, flag_low_swap_amount) + + inputStr := string(input) + if inputStr == "0" { + return res, nil + } + + userStore := h.userdataStore + metadata, err := store.GetSwapToVoucherData(ctx, userStore, sessionId, inputStr) + if err != nil { + return res, fmt.Errorf("failed to retrieve swap to voucher data: %v", err) + } + if metadata == nil { + res.FlagSet = append(res.FlagSet, flag_incorrect_voucher) + return res, nil + } + + // Store the active swap_to data + if err := store.UpdateSwapToVoucherData(ctx, userStore, sessionId, metadata); err != nil { + logg.ErrorCtxf(ctx, "failed on UpdateSwapToVoucherData", "error", err) + return res, err + } + + swapData, err := store.ReadSwapData(ctx, userStore, sessionId) + if err != nil { + return res, err + } + + // call the api using the ActivePoolAddress, ActiveSwapFromAddress, ActiveSwapToAddress and PublicKey to get the swap max limit + r, err := h.accountService.GetSwapFromTokenMaxLimit(ctx, swapData.ActivePoolAddress, swapData.ActiveSwapFromAddress, swapData.ActiveSwapToAddress, swapData.PublicKey) + if err != nil { + res.FlagSet = append(res.FlagSet, flag_api_error) + logg.ErrorCtxf(ctx, "failed on FetchTransactions", "error", err) + return res, err + } + + // Scale down the amount + maxAmountStr := store.ScaleDownBalance(r.Max, swapData.ActiveSwapFromDecimal) + if err != nil { + return res, err + } + + maxAmountFloat, err := strconv.ParseFloat(maxAmountStr, 64) + if err != nil { + logg.ErrorCtxf(ctx, "failed to parse maxAmountStr as float", "value", maxAmountStr, "error", err) + return res, err + } + + // Format to 2 decimal places + maxStr := fmt.Sprintf("%.2f", maxAmountFloat) + + if maxAmountFloat < 0.1 { + // return with low amount flag + res.Content = maxStr + res.FlagSet = append(res.FlagSet, flag_low_swap_amount) + return res, nil + } + + err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SWAP_MAX_AMOUNT, []byte(maxStr)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write swap max amount entry with", "key", storedb.DATA_ACTIVE_SWAP_MAX_AMOUNT, "value", maxStr, "error", err) + return res, err + } + + res.Content = fmt.Sprintf( + "Maximum: %s\n\nEnter amount of %s to swap for %s:", + maxStr, swapData.ActiveSwapFromSym, swapData.ActiveSwapToSym, + ) + + return res, nil +} + +// SwapPreview displays the swap preview and estimates +func (h *MenuHandlers) SwapPreview(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + sessionId, ok := ctx.Value("SessionId").(string) + if !ok { + return res, fmt.Errorf("missing session") + } + + inputStr := string(input) + if inputStr == "0" { + return res, nil + } + + flag_invalid_amount, _ := h.flagManager.GetFlag("flag_invalid_amount") + + code := codeFromCtx(ctx) + l := gotext.NewLocale(translationDir, code) + l.AddDomain("default") + + userStore := h.userdataStore + + swapData, err := store.ReadSwapPreviewData(ctx, userStore, sessionId) + if err != nil { + return res, err + } + + maxValue, err := strconv.ParseFloat(swapData.ActiveSwapMaxAmount, 64) + if err != nil { + logg.ErrorCtxf(ctx, "Failed to convert the swapMaxAmount to a float", "error", err) + return res, err + } + + inputAmount, err := strconv.ParseFloat(inputStr, 64) + if err != nil || inputAmount > maxValue { + res.FlagSet = append(res.FlagSet, flag_invalid_amount) + res.Content = inputStr + return res, nil + } + + finalAmountStr, err := store.ParseAndScaleAmount(inputStr, swapData.ActiveSwapFromDecimal) + if err != nil { + return res, err + } + + err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SWAP_AMOUNT, []byte(finalAmountStr)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write swap amount entry with", "key", storedb.DATA_ACTIVE_SWAP_AMOUNT, "value", finalAmountStr, "error", err) + return res, err + } + + // call the API to get the quote + r, err := h.accountService.GetPoolSwapQuote(ctx, finalAmountStr, swapData.PublicKey, swapData.ActiveSwapFromAddress, swapData.ActivePoolAddress, swapData.ActiveSwapToAddress) + if err != nil { + flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") + res.FlagSet = append(res.FlagSet, flag_api_error) + res.Content = l.Get("Your request failed. Please try again later.") + logg.ErrorCtxf(ctx, "failed on poolSwap", "error", err) + return res, nil + } + + // Scale down the quoted amount + quoteAmountStr := store.ScaleDownBalance(r.OutValue, swapData.ActiveSwapToDecimal) + qouteAmount, err := strconv.ParseFloat(quoteAmountStr, 64) + if err != nil { + logg.ErrorCtxf(ctx, "failed to parse quoteAmountStr as float", "value", quoteAmountStr, "error", err) + return res, err + } + + // Format to 2 decimal places + qouteStr := fmt.Sprintf("%.2f", qouteAmount) + + res.Content = fmt.Sprintf( + "You will swap:\n%s %s for %s %s:", + inputStr, swapData.ActiveSwapFromSym, qouteStr, swapData.ActiveSwapToSym, + ) + + return res, nil +} + +// InitiateSwap calls the poolSwap and returns a confirmation based on the result. +func (h *MenuHandlers) InitiateSwap(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + var err error + sessionId, ok := ctx.Value("SessionId").(string) + if !ok { + return res, fmt.Errorf("missing session") + } + + flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized") + + code := codeFromCtx(ctx) + l := gotext.NewLocale(translationDir, code) + l.AddDomain("default") + + userStore := h.userdataStore + + swapData, err := store.ReadSwapPreviewData(ctx, userStore, sessionId) + if err != nil { + return res, err + } + + swapAmount, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SWAP_AMOUNT) + if err != nil { + logg.ErrorCtxf(ctx, "failed to read swapAmount entry with", "key", storedb.DATA_ACTIVE_SWAP_AMOUNT, "error", err) + return res, err + } + + swapAmountStr := string(swapAmount) + + // Call the poolSwap API + r, err := h.accountService.PoolSwap(ctx, swapAmountStr, swapData.PublicKey, swapData.ActiveSwapFromAddress, swapData.ActivePoolAddress, swapData.ActiveSwapToAddress) + if err != nil { + flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") + res.FlagSet = append(res.FlagSet, flag_api_error) + res.Content = l.Get("Your request failed. Please try again later.") + logg.ErrorCtxf(ctx, "failed on poolSwap", "error", err) + return res, nil + } + + trackingId := r.TrackingId + logg.InfoCtxf(ctx, "poolSwap", "trackingId", trackingId) + + res.Content = l.Get( + "Your request has been sent. You will receive an SMS when your %s %s has been swapped for %s.", + swapAmountStr, + swapData.ActiveSwapFromSym, + swapData.ActiveSwapToSym, + ) + + res.FlagReset = append(res.FlagReset, flag_account_authorized) + return res, nil +} diff --git a/handlers/local.go b/handlers/local.go index 69f5883..ef96b20 100644 --- a/handlers/local.go +++ b/handlers/local.go @@ -130,7 +130,10 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountService) ls.DbRs.AddLocalFunc("confirm_new_alias", appHandlers.ConfirmNewAlias) ls.DbRs.AddLocalFunc("check_account_created", appHandlers.CheckAccountCreated) ls.DbRs.AddLocalFunc("reset_api_call_failure", appHandlers.ResetApiCallFailure) - + ls.DbRs.AddLocalFunc("swap_to_list", appHandlers.LoadSwapToList) + ls.DbRs.AddLocalFunc("swap_max_limit", appHandlers.SwapMaxLimit) + ls.DbRs.AddLocalFunc("swap_preview", appHandlers.SwapPreview) + ls.DbRs.AddLocalFunc("initiate_swap", appHandlers.InitiateSwap) ls.first = appHandlers.Init return appHandlers, nil diff --git a/services/registration/api_failure b/services/registration/api_failure index 915b7fc..47583ac 100644 --- a/services/registration/api_failure +++ b/services/registration/api_failure @@ -1 +1 @@ -Failed to connect to the custodial service .Please try again. \ No newline at end of file +Failed to connect to the custodial service. Please try again. \ No newline at end of file diff --git a/services/registration/locale/swa/default.po b/services/registration/locale/swa/default.po index cac4737..5e5d456 100644 --- a/services/registration/locale/swa/default.po +++ b/services/registration/locale/swa/default.po @@ -31,6 +31,11 @@ msgstr "Salio la Kikundi: 0.00" msgid "Symbol: %s\nBalance: %s" msgstr "Sarafu: %s\nSalio: %s" +msgid "Your request has been sent. You will receive an SMS when your %s %s has been swapped for %s." +msgstr "Ombi lako limetumwa. Utapokea SMS wakati %s %s yako itakapobadilishwa kuwa %s." msgid "%s balance: %s\n" -msgstr "%s salio: %s\n" \ No newline at end of file +msgstr "%s salio: %s\n" + +msgid "%s is not in the KENYA ROLA POOL. Please update your voucher and try again." +msgstr "%s haipo kwenye BWAWA LA KENYA ROLA. Tafadhali badilisha sarafu yako na ujaribu tena." \ No newline at end of file diff --git a/services/registration/low_swap_amount b/services/registration/low_swap_amount new file mode 100644 index 0000000..d3e50f7 --- /dev/null +++ b/services/registration/low_swap_amount @@ -0,0 +1 @@ +Available amount {{.swap_max_limit}} is too low, please try again: \ No newline at end of file diff --git a/services/registration/low_swap_amount.vis b/services/registration/low_swap_amount.vis new file mode 100644 index 0000000..336ccde --- /dev/null +++ b/services/registration/low_swap_amount.vis @@ -0,0 +1,6 @@ +MAP swap_max_limit +MOUT retry 1 +MOUT quit 9 +HALT +INCMP _ 1 +INCMP quit 9 diff --git a/services/registration/low_swap_amount_swa b/services/registration/low_swap_amount_swa new file mode 100644 index 0000000..2d11dd9 --- /dev/null +++ b/services/registration/low_swap_amount_swa @@ -0,0 +1 @@ +Kiasi kinachopatikana {{.swap_max_limit}} ni cha chini sana, tafadhali jaribu tena: \ No newline at end of file diff --git a/services/registration/main.vis b/services/registration/main.vis index efa8b2a..0eb9fc9 100644 --- a/services/registration/main.vis +++ b/services/registration/main.vis @@ -8,14 +8,16 @@ RELOAD check_balance CATCH api_failure flag_api_call_error 1 MAP check_balance MOUT send 1 -MOUT vouchers 2 -MOUT account 3 -MOUT help 4 +MOUT swap 2 +MOUT vouchers 3 +MOUT account 4 +MOUT help 5 MOUT quit 9 HALT INCMP send 1 -INCMP my_vouchers 2 -INCMP my_account 3 -INCMP help 4 +INCMP swap_to_list 2 +INCMP my_vouchers 3 +INCMP my_account 4 +INCMP help 5 INCMP quit 9 INCMP . * diff --git a/services/registration/missing_voucher b/services/registration/missing_voucher new file mode 100644 index 0000000..8b67f26 --- /dev/null +++ b/services/registration/missing_voucher @@ -0,0 +1 @@ +{{.swap_to_list}} \ No newline at end of file diff --git a/services/registration/missing_voucher.vis b/services/registration/missing_voucher.vis new file mode 100644 index 0000000..cf01c5b --- /dev/null +++ b/services/registration/missing_voucher.vis @@ -0,0 +1,6 @@ +MAP swap_to_list +MOUT back 0 +MOUT quit 9 +HALT +INCMP ^ 0 +INCMP quit 9 diff --git a/services/registration/missing_voucher_swa b/services/registration/missing_voucher_swa new file mode 100644 index 0000000..8b67f26 --- /dev/null +++ b/services/registration/missing_voucher_swa @@ -0,0 +1 @@ +{{.swap_to_list}} \ No newline at end of file diff --git a/services/registration/pp.csv b/services/registration/pp.csv index ff26614..fa67938 100644 --- a/services/registration/pp.csv +++ b/services/registration/pp.csv @@ -31,4 +31,7 @@ flag,flag_back_set,37,this is set when it is a back navigation flag,flag_account_blocked,38,this is set when an account has been blocked after the allowed incorrect PIN attempts have been exceeded flag,flag_invalid_pin,39,this is set when the given PIN is invalid(is less than or more than 4 digits) flag,flag_alias_set,40,this is set when an account alias has been assigned to a user -flag,flag_account_pin_reset,41,this is set on an account when an admin triggers a PIN reset for them +flag,flag_account_pin_reset,41,this is set on an account when an admin triggers a PIN reset for themflag,flag_incorrect_pool,39,this is set when the user selects an invalid pool +flag,flag_incorrect_pool,42,this is set when the user selects an invalid pool +flag,flag_low_swap_amount,43,this is set when the swap max limit is less than 0.1 + diff --git a/services/registration/swap_initiated.vis b/services/registration/swap_initiated.vis new file mode 100644 index 0000000..69ffc6d --- /dev/null +++ b/services/registration/swap_initiated.vis @@ -0,0 +1,3 @@ +LOAD reset_incorrect 6 +LOAD initiate_swap 0 +HALT diff --git a/services/registration/swap_limit b/services/registration/swap_limit new file mode 100644 index 0000000..4c9797c --- /dev/null +++ b/services/registration/swap_limit @@ -0,0 +1 @@ +{{.swap_max_limit}} \ No newline at end of file diff --git a/services/registration/swap_limit.vis b/services/registration/swap_limit.vis new file mode 100644 index 0000000..b96f1f5 --- /dev/null +++ b/services/registration/swap_limit.vis @@ -0,0 +1,6 @@ +RELOAD swap_max_limit +MAP swap_max_limit +MOUT back 0 +HALT +INCMP _ 0 +INCMP swap_preview * diff --git a/services/registration/swap_limit_swa b/services/registration/swap_limit_swa new file mode 100644 index 0000000..4c9797c --- /dev/null +++ b/services/registration/swap_limit_swa @@ -0,0 +1 @@ +{{.swap_max_limit}} \ No newline at end of file diff --git a/services/registration/swap_menu b/services/registration/swap_menu new file mode 100644 index 0000000..a5daabf --- /dev/null +++ b/services/registration/swap_menu @@ -0,0 +1 @@ +Swap \ No newline at end of file diff --git a/services/registration/swap_preview b/services/registration/swap_preview new file mode 100644 index 0000000..fb008e4 --- /dev/null +++ b/services/registration/swap_preview @@ -0,0 +1,3 @@ +{{.swap_preview}} + +Please enter your PIN to confirm: \ No newline at end of file diff --git a/services/registration/swap_preview.vis b/services/registration/swap_preview.vis new file mode 100644 index 0000000..901de26 --- /dev/null +++ b/services/registration/swap_preview.vis @@ -0,0 +1,12 @@ +LOAD swap_preview 0 +MAP swap_preview +MOUT back 0 +MOUT quit 9 +LOAD authorize_account 6 +HALT +RELOAD authorize_account +CATCH incorrect_pin flag_incorrect_pin 1 +CATCH . flag_account_authorized 0 +INCMP _ 0 +INCMP quit 9 +INCMP swap_initiated * diff --git a/services/registration/swap_preview_swa b/services/registration/swap_preview_swa new file mode 100644 index 0000000..4c6d567 --- /dev/null +++ b/services/registration/swap_preview_swa @@ -0,0 +1,3 @@ +{{.swap_preview}} + +Tafadhali weka PIN yako kudhibitisha: \ No newline at end of file diff --git a/services/registration/swap_to_list b/services/registration/swap_to_list new file mode 100644 index 0000000..86f56c3 --- /dev/null +++ b/services/registration/swap_to_list @@ -0,0 +1,2 @@ +Select number or symbol to swap TO: +{{.swap_to_list}} \ No newline at end of file diff --git a/services/registration/swap_to_list.vis b/services/registration/swap_to_list.vis new file mode 100644 index 0000000..a2d3bea --- /dev/null +++ b/services/registration/swap_to_list.vis @@ -0,0 +1,12 @@ +LOAD swap_to_list 0 +RELOAD swap_to_list +MAP swap_to_list +CATCH missing_voucher flag_incorrect_voucher 1 +MOUT back 0 +HALT +LOAD swap_max_limit 64 +RELOAD swap_max_limit +CATCH . flag_incorrect_voucher 1 +CATCH low_swap_amount flag_low_swap_amount 1 +INCMP _ 0 +INCMP swap_limit * diff --git a/services/registration/swap_to_list_swa b/services/registration/swap_to_list_swa new file mode 100644 index 0000000..42da7d3 --- /dev/null +++ b/services/registration/swap_to_list_swa @@ -0,0 +1,2 @@ +Chagua nambari au ishara ya sarafu kubadilisha KWENDA: +{{.swap_to_list}} \ No newline at end of file diff --git a/store/db/db.go b/store/db/db.go index 517d0a0..e6e1eef 100644 --- a/store/db/db.go +++ b/store/db/db.go @@ -67,6 +67,24 @@ const ( DATA_SUGGESTED_ALIAS //Key used to store a value of 1 for a user to reset their own PIN once they access the menu. DATA_SELF_PIN_RESET + // Holds the active pool contract address for the swap + DATA_ACTIVE_POOL_ADDRESS + // Currently active swap from symbol for the swap + DATA_ACTIVE_SWAP_FROM_SYM + // Currently active swap from decimal count for the swap + DATA_ACTIVE_SWAP_FROM_DECIMAL + // Holds the active swap from contract address for the swap + DATA_ACTIVE_SWAP_FROM_ADDRESS + // Currently active swap from to for the swap + DATA_ACTIVE_SWAP_TO_SYM + // Currently active swap to decimal count for the swap + DATA_ACTIVE_SWAP_TO_DECIMAL + // Holds the active pool contract address for the swap + DATA_ACTIVE_SWAP_TO_ADDRESS + // Holds the max swap amount for the swap + DATA_ACTIVE_SWAP_MAX_AMOUNT + // Holds the active swap amount for the swap + DATA_ACTIVE_SWAP_AMOUNT ) const ( @@ -105,6 +123,31 @@ const ( DATA_TRANSACTIONS = 1024 + iota ) +const ( + // List of voucher symbols in the top pools context. + DATA_POOL_NAMES = 2048 + iota + // List of symbols in the top pools context. + DATA_POOL_SYMBOLS + // List of contact addresses in the top pools context + DATA_POOL_ADDRESSES + // List of swap from voucher symbols in the user context. + DATA_POOL_FROM_SYMBOLS + // List of swap from balances for vouchers valid in the pools context. + DATA_POOL_FROM_BALANCES + // List of swap from decimal counts for vouchers valid in the pools context. + DATA_POOL_FROM_DECIMALS + // List of swap from EVM addresses for vouchers valid in the pools context. + DATA_POOL_FROM_ADDRESSES + // List of swap to voucher symbols in the user context. + DATA_POOL_TO_SYMBOLS + // List of swap to balances for vouchers valid in the pools context. + DATA_POOL_TO_BALANCES + // List of swap to decimal counts for vouchers valid in the pools context. + DATA_POOL_TO_DECIMALS + // List of swap to EVM addresses for vouchers valid in the pools context. + DATA_POOL_TO_ADDRESSES +) + var ( logg = logging.NewVanilla().WithDomain("urdt-common") ) diff --git a/store/pools.go b/store/pools.go new file mode 100644 index 0000000..1e01329 --- /dev/null +++ b/store/pools.go @@ -0,0 +1,93 @@ +package store + +import ( + "context" + "fmt" + "strings" + + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" + dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" +) + +// PoolsMetadata helps organize data fields +type PoolsMetadata struct { + PoolNames string + PoolSymbols string + PoolContractAdrresses string +} + +// ProcessPools converts pools into formatted strings +func ProcessPools(pools []dataserviceapi.PoolDetails) PoolsMetadata { + var data PoolsMetadata + var poolNames, poolSymbols, poolContractAdrresses []string + + for i, p := range pools { + poolNames = append(poolNames, fmt.Sprintf("%d:%s", i+1, p.PoolName)) + poolSymbols = append(poolSymbols, fmt.Sprintf("%d:%s", i+1, p.PoolSymbol)) + poolContractAdrresses = append(poolContractAdrresses, fmt.Sprintf("%d:%s", i+1, p.PoolContractAdrress)) + } + + data.PoolNames = strings.Join(poolNames, "\n") + data.PoolSymbols = strings.Join(poolSymbols, "\n") + data.PoolContractAdrresses = strings.Join(poolContractAdrresses, "\n") + + return data +} + +// GetPoolData retrieves and matches pool data +// if no match is found, it fetches the API with the symbol +func GetPoolData(ctx context.Context, store DataStore, sessionId string, input string) (*dataserviceapi.PoolDetails, error) { + keys := []storedb.DataTyp{ + storedb.DATA_POOL_NAMES, + storedb.DATA_POOL_SYMBOLS, + storedb.DATA_POOL_ADDRESSES, + } + data := make(map[storedb.DataTyp]string) + + for _, key := range keys { + value, err := store.ReadEntry(ctx, sessionId, key) + if err != nil { + return nil, fmt.Errorf("failed to get data key %x: %v", key, err) + } + data[key] = string(value) + } + + name, symbol, address := MatchPool(input, + data[storedb.DATA_POOL_NAMES], + data[storedb.DATA_POOL_SYMBOLS], + data[storedb.DATA_POOL_ADDRESSES], + ) + + if symbol == "" { + return nil, nil + } + + return &dataserviceapi.PoolDetails{ + PoolName: string(name), + PoolSymbol: string(symbol), + PoolContractAdrress: string(address), + }, nil +} + +// MatchPool finds the matching pool name, symbol and pool contract address based on the input. +func MatchPool(input, names, symbols, addresses string) (name, symbol, address string) { + nameList := strings.Split(names, "\n") + symList := strings.Split(symbols, "\n") + addrList := strings.Split(addresses, "\n") + + for i, sym := range symList { + parts := strings.SplitN(sym, ":", 2) + + if input == parts[0] || strings.EqualFold(input, parts[1]) { + symbol = parts[1] + if i < len(nameList) { + name = strings.SplitN(nameList[i], ":", 2)[1] + } + if i < len(addrList) { + address = strings.SplitN(addrList[i], ":", 2)[1] + } + break + } + } + return +} diff --git a/store/swap.go b/store/swap.go new file mode 100644 index 0000000..fc0a569 --- /dev/null +++ b/store/swap.go @@ -0,0 +1,187 @@ +package store + +import ( + "context" + "errors" + "fmt" + "reflect" + + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" + dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" +) + +type SwapData struct { + PublicKey string + ActivePoolAddress string + ActiveSwapFromSym string + ActiveSwapFromDecimal string + ActiveSwapFromAddress string + ActiveSwapToSym string + ActiveSwapToAddress string +} + +type SwapPreviewData struct { + PublicKey string + ActiveSwapMaxAmount string + ActiveSwapFromDecimal string + ActivePoolAddress string + ActiveSwapFromAddress string + ActiveSwapFromSym string + ActiveSwapToAddress string + ActiveSwapToSym string + ActiveSwapToDecimal string +} + +func ReadSwapData(ctx context.Context, store DataStore, sessionId string) (SwapData, error) { + data := SwapData{} + fieldToKey := map[string]storedb.DataTyp{ + "PublicKey": storedb.DATA_PUBLIC_KEY, + "ActivePoolAddress": storedb.DATA_ACTIVE_POOL_ADDRESS, + "ActiveSwapFromSym": storedb.DATA_ACTIVE_SYM, + "ActiveSwapFromDecimal": storedb.DATA_ACTIVE_DECIMAL, + "ActiveSwapFromAddress": storedb.DATA_ACTIVE_ADDRESS, + "ActiveSwapToSym": storedb.DATA_ACTIVE_SWAP_TO_SYM, + "ActiveSwapToAddress": storedb.DATA_ACTIVE_SWAP_TO_ADDRESS, + } + + v := reflect.ValueOf(&data).Elem() + for fieldName, key := range fieldToKey { + field := v.FieldByName(fieldName) + if !field.IsValid() || !field.CanSet() { + return data, errors.New("invalid struct field: " + fieldName) + } + + value, err := ReadStringEntry(ctx, store, sessionId, key) + if err != nil { + return data, err + } + field.SetString(value) + } + + return data, nil +} + +func ReadSwapPreviewData(ctx context.Context, store DataStore, sessionId string) (SwapPreviewData, error) { + data := SwapPreviewData{} + fieldToKey := map[string]storedb.DataTyp{ + "PublicKey": storedb.DATA_PUBLIC_KEY, + "ActiveSwapMaxAmount": storedb.DATA_ACTIVE_SWAP_MAX_AMOUNT, + "ActiveSwapFromDecimal": storedb.DATA_ACTIVE_DECIMAL, + "ActivePoolAddress": storedb.DATA_ACTIVE_POOL_ADDRESS, + "ActiveSwapFromAddress": storedb.DATA_ACTIVE_ADDRESS, + "ActiveSwapFromSym": storedb.DATA_ACTIVE_SYM, + "ActiveSwapToAddress": storedb.DATA_ACTIVE_SWAP_TO_ADDRESS, + "ActiveSwapToSym": storedb.DATA_ACTIVE_SWAP_TO_SYM, + "ActiveSwapToDecimal": storedb.DATA_ACTIVE_SWAP_TO_DECIMAL, + } + + v := reflect.ValueOf(&data).Elem() + for fieldName, key := range fieldToKey { + field := v.FieldByName(fieldName) + if !field.IsValid() || !field.CanSet() { + return data, errors.New("invalid struct field: " + fieldName) + } + + value, err := ReadStringEntry(ctx, store, sessionId, key) + if err != nil { + return data, err + } + field.SetString(value) + } + + return data, nil +} + +// GetSwapFromVoucherData retrieves and matches swap from voucher data +func GetSwapFromVoucherData(ctx context.Context, store DataStore, sessionId string, input string) (*dataserviceapi.TokenHoldings, error) { + keys := []storedb.DataTyp{ + storedb.DATA_POOL_FROM_SYMBOLS, + storedb.DATA_POOL_FROM_BALANCES, + storedb.DATA_POOL_FROM_DECIMALS, + storedb.DATA_POOL_FROM_ADDRESSES, + } + data := make(map[storedb.DataTyp]string) + + for _, key := range keys { + value, err := store.ReadEntry(ctx, sessionId, key) + if err != nil { + return nil, fmt.Errorf("failed to get data key %x: %v", key, err) + } + data[key] = string(value) + } + + symbol, balance, decimal, address := MatchVoucher(input, + data[storedb.DATA_POOL_FROM_SYMBOLS], + data[storedb.DATA_POOL_FROM_BALANCES], + data[storedb.DATA_POOL_FROM_DECIMALS], + data[storedb.DATA_POOL_FROM_ADDRESSES], + ) + + if symbol == "" { + return nil, nil + } + + return &dataserviceapi.TokenHoldings{ + TokenSymbol: string(symbol), + Balance: string(balance), + TokenDecimals: string(decimal), + ContractAddress: string(address), + }, nil +} + +// GetSwapToVoucherData retrieves and matches voucher data +func GetSwapToVoucherData(ctx context.Context, store DataStore, sessionId string, input string) (*dataserviceapi.TokenHoldings, error) { + keys := []storedb.DataTyp{ + storedb.DATA_POOL_TO_SYMBOLS, + storedb.DATA_POOL_TO_BALANCES, + storedb.DATA_POOL_TO_DECIMALS, + storedb.DATA_POOL_TO_ADDRESSES, + } + data := make(map[storedb.DataTyp]string) + + for _, key := range keys { + value, err := store.ReadEntry(ctx, sessionId, key) + if err != nil { + return nil, fmt.Errorf("failed to get data key %x: %v", key, err) + } + data[key] = string(value) + } + + symbol, balance, decimal, address := MatchVoucher(input, + data[storedb.DATA_POOL_TO_SYMBOLS], + data[storedb.DATA_POOL_TO_BALANCES], + data[storedb.DATA_POOL_TO_DECIMALS], + data[storedb.DATA_POOL_TO_ADDRESSES], + ) + + if symbol == "" { + return nil, nil + } + + return &dataserviceapi.TokenHoldings{ + TokenSymbol: string(symbol), + Balance: string(balance), + TokenDecimals: string(decimal), + ContractAddress: string(address), + }, nil +} + +// UpdateSwapToVoucherData updates the active swap to voucher data in the DataStore. +func UpdateSwapToVoucherData(ctx context.Context, store DataStore, sessionId string, data *dataserviceapi.TokenHoldings) error { + logg.TraceCtxf(ctx, "dtal", "data", data) + // Active swap to voucher data entries + activeEntries := map[storedb.DataTyp][]byte{ + storedb.DATA_ACTIVE_SWAP_TO_SYM: []byte(data.TokenSymbol), + storedb.DATA_ACTIVE_SWAP_TO_DECIMAL: []byte(data.TokenDecimals), + storedb.DATA_ACTIVE_SWAP_TO_ADDRESS: []byte(data.ContractAddress), + } + + // Write active data + for key, value := range activeEntries { + if err := store.WriteEntry(ctx, sessionId, key, value); err != nil { + return err + } + } + + return nil +} diff --git a/store/swap_test.go b/store/swap_test.go new file mode 100644 index 0000000..179eeee --- /dev/null +++ b/store/swap_test.go @@ -0,0 +1,146 @@ +package store + +import ( + "testing" + + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" + "github.com/alecthomas/assert/v2" +) + +func TestReadSwapData(t *testing.T) { + sessionId := "session123" + publicKey := "0X13242618721" + ctx, store := InitializeTestDb(t) + + // Test swap data + swapData := map[storedb.DataTyp]string{ + storedb.DATA_PUBLIC_KEY: publicKey, + storedb.DATA_ACTIVE_POOL_ADDRESS: "0x48a953cA5cf5298bc6f6Af3C608351f537AAcb9e", + storedb.DATA_ACTIVE_SWAP_FROM_SYM: "AMANI", + storedb.DATA_ACTIVE_SWAP_FROM_DECIMAL: "6", + storedb.DATA_ACTIVE_SWAP_FROM_ADDRESS: "0xc7B78Ac9ACB9E025C8234621FC515bC58179dEAe", + storedb.DATA_ACTIVE_SWAP_TO_SYM: "cUSD", + storedb.DATA_ACTIVE_SWAP_TO_ADDRESS: "0x765DE816845861e75A25fCA122bb6898B8B1282a", + } + + // Store the data + for key, value := range swapData { + if err := store.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil { + t.Fatal(err) + } + } + + expectedResult := SwapData{ + PublicKey: "0X13242618721", + ActivePoolAddress: "0x48a953cA5cf5298bc6f6Af3C608351f537AAcb9e", + ActiveSwapFromSym: "AMANI", + ActiveSwapFromDecimal: "6", + ActiveSwapFromAddress: "0xc7B78Ac9ACB9E025C8234621FC515bC58179dEAe", + ActiveSwapToSym: "cUSD", + ActiveSwapToAddress: "0x765DE816845861e75A25fCA122bb6898B8B1282a", + } + + data, err := ReadSwapData(ctx, store, sessionId) + + assert.NoError(t, err) + assert.Equal(t, expectedResult, data) +} + +func TestReadSwapPreviewData(t *testing.T) { + sessionId := "session123" + publicKey := "0X13242618721" + ctx, store := InitializeTestDb(t) + + // Test swap preview data + swapPreviewData := map[storedb.DataTyp]string{ + storedb.DATA_PUBLIC_KEY: publicKey, + storedb.DATA_ACTIVE_SWAP_MAX_AMOUNT: "1339482", + storedb.DATA_ACTIVE_SWAP_FROM_DECIMAL: "6", + storedb.DATA_ACTIVE_POOL_ADDRESS: "0x48a953cA5cf5298bc6f6Af3C608351f537AAcb9e", + storedb.DATA_ACTIVE_SWAP_FROM_ADDRESS: "0xc7B78Ac9ACB9E025C8234621FC515bC58179dEAe", + storedb.DATA_ACTIVE_SWAP_FROM_SYM: "AMANI", + storedb.DATA_ACTIVE_SWAP_TO_ADDRESS: "0x765DE816845861e75A25fCA122bb6898B8B1282a", + storedb.DATA_ACTIVE_SWAP_TO_SYM: "cUSD", + storedb.DATA_ACTIVE_SWAP_TO_DECIMAL: "18", + } + + // Store the data + for key, value := range swapPreviewData { + if err := store.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil { + t.Fatal(err) + } + } + + expectedResult := SwapPreviewData{ + PublicKey: "0X13242618721", + ActiveSwapMaxAmount: "1339482", + ActiveSwapFromDecimal: "6", + ActivePoolAddress: "0x48a953cA5cf5298bc6f6Af3C608351f537AAcb9e", + ActiveSwapFromAddress: "0xc7B78Ac9ACB9E025C8234621FC515bC58179dEAe", + ActiveSwapFromSym: "AMANI", + ActiveSwapToAddress: "0x765DE816845861e75A25fCA122bb6898B8B1282a", + ActiveSwapToSym: "cUSD", + ActiveSwapToDecimal: "18", + } + + data, err := ReadSwapPreviewData(ctx, store, sessionId) + + assert.NoError(t, err) + assert.Equal(t, expectedResult, data) +} + +func TestGetSwapFromVoucherData(t *testing.T) { + sessionId := "session123" + ctx, store := InitializeTestDb(t) + + // Test pool swap data + mockData := map[storedb.DataTyp][]byte{ + storedb.DATA_POOL_FROM_SYMBOLS: []byte("1:AMANI\n2:AMUA"), + storedb.DATA_POOL_FROM_BALANCES: []byte("1:\n2:"), + storedb.DATA_POOL_FROM_DECIMALS: []byte("1:6\n2:4"), + storedb.DATA_POOL_FROM_ADDRESSES: []byte("1:0xc7B78Ac9ACB9E025C8234621FC515bC58179dEAe\n2:0xF0C3C7581b8b96B59a97daEc8Bd48247cE078674"), + } + + // Put the data + for key, value := range mockData { + if err := store.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil { + t.Fatal(err) + } + } + + result, err := GetSwapFromVoucherData(ctx, store, sessionId, "1") + + assert.NoError(t, err) + assert.Equal(t, "AMANI", result.TokenSymbol) + assert.Equal(t, "", result.Balance) + assert.Equal(t, "6", result.TokenDecimals) + assert.Equal(t, "0xc7B78Ac9ACB9E025C8234621FC515bC58179dEAe", result.ContractAddress) +} + +func TestGetSwapToVoucherData(t *testing.T) { + sessionId := "session123" + ctx, store := InitializeTestDb(t) + + // Test pool swap data + mockData := map[storedb.DataTyp][]byte{ + storedb.DATA_POOL_TO_SYMBOLS: []byte("1:cUSD\n2:AMUA"), + storedb.DATA_POOL_TO_BALANCES: []byte("1:\n2:"), + storedb.DATA_POOL_TO_DECIMALS: []byte("1:6\n2:4"), + storedb.DATA_POOL_TO_ADDRESSES: []byte("1:0xc7B78Ac9ACB9E025C8234621\n2:0xF0C3C7581b8b96B59a97daEc8Bd48247cE078674"), + } + + // Put the data + for key, value := range mockData { + if err := store.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil { + t.Fatal(err) + } + } + + result, err := GetSwapToVoucherData(ctx, store, sessionId, "1") + + assert.NoError(t, err) + assert.Equal(t, "cUSD", result.TokenSymbol) + assert.Equal(t, "", result.Balance) + assert.Equal(t, "6", result.TokenDecimals) + assert.Equal(t, "0xc7B78Ac9ACB9E025C8234621", result.ContractAddress) +} diff --git a/store/tokens.go b/store/tokens.go index 7c3ad0c..a7770c7 100644 --- a/store/tokens.go +++ b/store/tokens.go @@ -64,7 +64,7 @@ func ReadTransactionData(ctx context.Context, store DataStore, sessionId string) return data, errors.New("invalid struct field: " + fieldName) } - value, err := readStringEntry(ctx, store, sessionId, key) + value, err := ReadStringEntry(ctx, store, sessionId, key) if err != nil { return data, err } @@ -74,7 +74,7 @@ func ReadTransactionData(ctx context.Context, store DataStore, sessionId string) return data, nil } -func readStringEntry(ctx context.Context, store DataStore, sessionId string, key storedb.DataTyp) (string, error) { +func ReadStringEntry(ctx context.Context, store DataStore, sessionId string, key storedb.DataTyp) (string, error) { entry, err := store.ReadEntry(ctx, sessionId, key) if err != nil { return "", err