Compare commits

...

46 Commits

Author SHA1 Message Date
d60f5461e6 Merge branch 'master' into alfred/pool-swap 2025-05-19 16:44:16 +03:00
35d562ffdd
use a default pool and the active voucher to perform a swap 2025-05-19 03:13:09 +03:00
7d8631fa87
use the userstore instead of the prefixdb 2025-05-19 02:30:12 +03:00
c8159113f0
updated the correct swap data keys and use the userstore instead of the prefixdb 2025-05-19 02:29:47 +03:00
126415050c
removed unused functions 2025-05-19 02:28:01 +03:00
5b989d83e1
fix UI 2025-05-19 02:27:23 +03:00
7199d0f3e0
add a message and translation for the missing voucher in the default pool 2025-05-19 02:26:37 +03:00
feea74c2c9
add a RELOAD for the back navigation from swap_limit 2025-05-19 02:25:40 +03:00
b6dcf65ea3
add a catch fow when the active voucher is not in the default pool 2025-05-19 02:24:42 +03:00
da7b6e93e1
navigate directly to the swap_to_list 2025-05-19 02:21:56 +03:00
97be43b9ec
use updated sarafu-api package 2025-05-19 02:00:40 +03:00
a9ecb1bbae
removed unused vis files and templates 2025-05-19 01:59:59 +03:00
7e2e3c3722
upgMerge branch 'master' into alfred/pool-swap 2025-05-14 12:15:40 +03:00
724d11b4db
Added TestGetSwapToVoucherData 2025-03-13 17:09:00 +03:00
2ace3606bc
Added TestUpdateSwapFromVoucherData 2025-03-13 13:19:58 +03:00
ab5299fae2
Added TestGetSwapFromVoucherData 2025-03-13 13:15:38 +03:00
c407dd1702
Added TestReadSwapPreviewData 2025-03-13 13:10:58 +03:00
9729a6442b
Added TestReadSwapData 2025-03-13 13:05:51 +03:00
fd0c9cbc68
Updated sarafu-api package 2025-03-13 10:17:16 +03:00
2eef202a1a
Capitalize the Swap menu option 2025-03-13 09:30:57 +03:00
1df102db9f
Updated the SwapMaxLimit func to use the GetSwapFromTokenMaxLimit and set a flag when the max limit is invalid 2025-03-12 10:48:36 +03:00
8292f76334
Updated the sarafu-api package 2025-03-12 10:46:45 +03:00
ec8dc2fb1d
Added a catch for the flag_low_swap_amount 2025-03-12 10:46:11 +03:00
6a91049ffb
Added a flag_low_swap_amount and vis files for when the swap max limit is less than 0.1 2025-03-12 10:45:38 +03:00
d13d24f032
Update the conflicting input numbers on main.vis 2025-03-12 10:44:09 +03:00
46d5c2f035
pass the correct arguments to the swap functions 2025-03-10 17:16:44 +03:00
69ea52d91c
use data fetched from sarafu-api 2025-03-10 16:48:19 +03:00
450cba07c7
Use the latest sarafu-api 2025-03-10 16:45:04 +03:00
16380dbe49
Use the GetPoolSwapQuote and PoolSwap functions for live data 2025-03-06 18:58:25 +03:00
2a46de9a6f
Use the latest sarafu-api changes with pool swap functionality 2025-03-06 18:57:24 +03:00
baff75da50
Add swap functionality using dummy data 2025-03-06 09:25:55 +03:00
d6047827a4
link swap related functions 2025-03-06 09:24:48 +03:00
6642fc0137
added the swap_initiated vis node 2025-03-06 09:23:53 +03:00
c367984930
move the catch for flag_account_authorized to the swap_preview node 2025-03-06 09:23:28 +03:00
21922e2b62
added translation for the swap request confirmation 2025-03-06 09:22:53 +03:00
a21186bd59
use a single swap.go file for swap helper functions 2025-03-05 17:00:44 +03:00
28eefb6197
make the ReadStringEntry func accessible 2025-03-05 16:36:37 +03:00
3e9a67b34e
added the swap preview vis files 2025-03-04 21:21:59 +03:00
2a2d43c279
added the swap limit vis files 2025-03-04 21:21:40 +03:00
a1f0e4dcfe
added functions to process the pool data and match based on the provided input 2025-03-04 21:21:02 +03:00
ee07bf11f8
added helper functions to process the swap to and swap from voucher data 2025-03-04 21:20:05 +03:00
cb1d129eda
added swap db keys 2025-03-04 21:18:25 +03:00
e8978413a5
added pool db processing 2025-02-26 13:10:15 +03:00
566503956d
added pool_swap menu nodes 2025-02-26 13:09:29 +03:00
c5dbe966c4
added flag_incorrect_pool flag 2025-02-26 13:08:00 +03:00
1221372424
use latest ussd-data-service package 2025-02-26 13:07:11 +03:00
30 changed files with 943 additions and 14 deletions

4
go.mod
View File

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

10
go.sum
View File

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

View File

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

View File

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

View File

@ -1 +1 @@
Failed to connect to the custodial service .Please try again.
Failed to connect to the custodial service. Please try again.

View File

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

View File

@ -0,0 +1 @@
Available amount {{.swap_max_limit}} is too low, please try again:

View File

@ -0,0 +1,6 @@
MAP swap_max_limit
MOUT retry 1
MOUT quit 9
HALT
INCMP _ 1
INCMP quit 9

View File

@ -0,0 +1 @@
Kiasi kinachopatikana {{.swap_max_limit}} ni cha chini sana, tafadhali jaribu tena:

View File

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

View File

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

View File

@ -0,0 +1,6 @@
MAP swap_to_list
MOUT back 0
MOUT quit 9
HALT
INCMP ^ 0
INCMP quit 9

View File

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

View File

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

Can't render this file because it has a wrong number of fields in line 34.

View File

@ -0,0 +1,3 @@
LOAD reset_incorrect 6
LOAD initiate_swap 0
HALT

View File

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

View File

@ -0,0 +1,6 @@
RELOAD swap_max_limit
MAP swap_max_limit
MOUT back 0
HALT
INCMP _ 0
INCMP swap_preview *

View File

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

View File

@ -0,0 +1 @@
Swap

View File

@ -0,0 +1,3 @@
{{.swap_preview}}
Please enter your PIN to confirm:

View File

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

View File

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

View File

@ -0,0 +1,2 @@
Select number or symbol to swap TO:
{{.swap_to_list}}

View File

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

View File

@ -0,0 +1,2 @@
Chagua nambari au ishara ya sarafu kubadilisha KWENDA:
{{.swap_to_list}}

View File

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

93
store/pools.go Normal file
View File

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

187
store/swap.go Normal file
View File

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

146
store/swap_test.go Normal file
View File

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

View File

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