WIP: alfred/pool-swap #33

Draft
Alfred-mk wants to merge 33 commits from alfred/pool-swap into master
32 changed files with 1103 additions and 11 deletions

4
go.mod
View File

@ -5,12 +5,12 @@ go 1.23.4
require (
git.defalsify.org/vise.git v0.2.3-0.20250205173834-d1f6647211ac
git.grassecon.net/grassrootseconomics/common v0.0.0-20250121134736-ba8cbbccea7d
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250206112944-31eb30de0f69
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250313071411-ba43610ff00b
git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.1.0.20250204132347-1eb0b1555244
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.0-beta
github.com/jackc/pgx/v5 v5.7.1
github.com/peteole/testdata-loader v0.3.0
github.com/stretchr/testify v1.9.0

22
go.sum
View File

@ -4,6 +4,26 @@ git.grassecon.net/grassrootseconomics/common v0.0.0-20250121134736-ba8cbbccea7d
git.grassecon.net/grassrootseconomics/common v0.0.0-20250121134736-ba8cbbccea7d/go.mod h1:wgQJZGIS6QuNLHqDhcsvehsbn5PvgV7aziRebMnJi60=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250206112944-31eb30de0f69 h1:cbBpm9uNJak58MpFpNXJuvgCmz+A8kquXr9har4expg=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250206112944-31eb30de0f69/go.mod h1:gOn89ipaDcDvmQXRMQYKUqcw/sJcwVOPVt2eC6Geip8=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250224114958-1f3ac220d126 h1:fXL9uXt8yiu5ImJnwiHoLsq8T2TJ6+8qa97pCt+gHxA=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250224114958-1f3ac220d126/go.mod h1:gOn89ipaDcDvmQXRMQYKUqcw/sJcwVOPVt2eC6Geip8=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250306151434-35f24b518375 h1:VilYLeBFXxOSgs+AD9B1+Mu+VJZHiSWACY5xF1x3xBE=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250306151434-35f24b518375/go.mod h1:K/TPgZ4OhPHBQq2X0ab3JZs4YjiexzSURZcfHLs9Pf4=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250307051649-4783e2dcb319 h1:Qg6ttFJ4zjF67PERvLcXJ4/bXUTdCaRJ96RFRmQZ4xc=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250307051649-4783e2dcb319/go.mod h1:K/TPgZ4OhPHBQq2X0ab3JZs4YjiexzSURZcfHLs9Pf4=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250310110330-a04f7ee66c35 h1:JeRpSf+/NTFTl3lhiSkHO54NIoK6KrXmyqawfiuKc0Y=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250310110330-a04f7ee66c35/go.mod h1:K/TPgZ4OhPHBQq2X0ab3JZs4YjiexzSURZcfHLs9Pf4=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250310111703-e3b6c25792c2 h1:QhiSpx3ndgQeR/CAynLdiOR+wNEJJDFWMKE2CHmp8n8=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250310111703-e3b6c25792c2/go.mod h1:K/TPgZ4OhPHBQq2X0ab3JZs4YjiexzSURZcfHLs9Pf4=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250310133937-ba8d2a19c2ed h1:r5w89jInk9wwJZ4kMmE8ENPV7chxORJWIfxsPmnAQwY=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250310133937-ba8d2a19c2ed/go.mod h1:K/TPgZ4OhPHBQq2X0ab3JZs4YjiexzSURZcfHLs9Pf4=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250310141205-3b39b86d0987 h1:HXFD3Pabi1H4tuKVZUjyqsYoI8JrhJrH0NXeVuB2buo=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250310141205-3b39b86d0987/go.mod h1:K/TPgZ4OhPHBQq2X0ab3JZs4YjiexzSURZcfHLs9Pf4=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250311124651-3244c717cbee h1:YONuCBfzDlpeXsQMWRiBrRlSC0YHWUNuC5Sit43Ncww=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250311124651-3244c717cbee/go.mod h1:K/TPgZ4OhPHBQq2X0ab3JZs4YjiexzSURZcfHLs9Pf4=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250313070549-3c2889ac72f0 h1:AMa2VmeRrmHz87+RtH9sJGTGnlYXqkhJG1tX/vgQAx0=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250313070549-3c2889ac72f0/go.mod h1:K/TPgZ4OhPHBQq2X0ab3JZs4YjiexzSURZcfHLs9Pf4=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250313071411-ba43610ff00b h1:+j02QfOVYvPteaIpL/3KQ6taQnoI7TFkcH23iBUhoZg=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250313071411-ba43610ff00b/go.mod h1:K/TPgZ4OhPHBQq2X0ab3JZs4YjiexzSURZcfHLs9Pf4=
git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.1.0.20250204132347-1eb0b1555244 h1:BXotWSKg04U97sf/xeWJuUTSVgKk2aEK+5BtBrnafXQ=
git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.1.0.20250204132347-1eb0b1555244/go.mod h1:6B6ByxXOiRY0NR7K02Bf3fEu7z+2c/6q8PFVNjC5G8w=
git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694 h1:DjJlBSz0S13acft5XZDWk7ZYnzElym0xLMYEVgyNJ+E=
@ -27,6 +47,8 @@ github.com/grassrootseconomics/eth-custodial v1.3.0-beta h1:twrMBhl89GqDUL9PlkzQ
github.com/grassrootseconomics/eth-custodial v1.3.0-beta/go.mod h1:7uhRcdnJplX4t6GKCEFkbeDhhjlcaGJeJqevbcvGLZo=
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/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

@ -2412,3 +2412,436 @@ 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
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,
}
for key, value := range dataMap {
if err := h.prefixDb.Put(ctx, []byte(storedb.ToBytes(key)), []byte(value)); err != nil {
return res, nil
}
}
res.Content = h.ReplaceSeparatorFunc(data.PoolSymbols)
return res, nil
}
// LoadSwapFromList gets all the possible tokens a user can select to swap from
func (h *MenuHandlers) LoadSwapFromList(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
}
flag_incorrect_pool, _ := h.flagManager.GetFlag("flag_incorrect_pool")
flag_api_error, _ := h.flagManager.GetFlag("flag_api_error")
inputStr := string(input)
if inputStr == "0" {
res.FlagReset = append(res.FlagReset, flag_incorrect_pool)
return res, nil
}
poolData, err := store.GetPoolData(ctx, h.prefixDb, inputStr)
if err != nil {
return res, fmt.Errorf("failed to retrieve pool data: %v", err)
}
if poolData == nil {
// no match found. Call the API
poolResp := dataserviceapi.PoolDetails{
PoolName: "DevTest",
PoolSymbol: "DEVT",
PoolContractAdrress: "0x145F87d6198dEDD45C614FFD8b70E9a2fCCc5cc9",
LimiterAddress: "",
VoucherRegistry: "",
}
if (poolResp == dataserviceapi.PoolDetails{}) {
// If the API does not return the data, set the flag
res.FlagSet = append(res.FlagSet, flag_incorrect_pool)
return res, nil
}
poolData = &poolResp
}
activePoolAddress := poolData.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
}
res.FlagReset = append(res.FlagReset, flag_incorrect_pool)
// call the api using the activePoolAddress and publicKey to get a list of SwapfromSymbolsData
swapFromList, err := h.accountService.GetPoolSwappableFromVouchers(ctx, 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(swapFromList) == 0 {
res.FlagSet = append(res.FlagSet, flag_incorrect_pool)
return res, nil
}
data := store.ProcessVouchers(swapFromList)
// Store all from list voucher data
dataMap := map[storedb.DataTyp]string{
storedb.DATA_POOL_FROM_SYMBOLS: data.Symbols,
storedb.DATA_POOL_FROM_BALANCES: data.Balances,
storedb.DATA_POOL_FROM_DECIMALS: data.Decimals,
storedb.DATA_POOL_FROM_ADDRESSES: data.Addresses,
}
for key, value := range dataMap {
if err := h.prefixDb.Put(ctx, []byte(storedb.ToBytes(key)), []byte(value)); err != nil {
return res, nil
}
}
res.Content = h.ReplaceSeparatorFunc(data.Symbols)
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
}
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
}
metadata, err := store.GetSwapFromVoucherData(ctx, h.prefixDb, inputStr)
if err != nil {
return res, fmt.Errorf("failed to retrieve swap from voucher data: %v", err)
}
if metadata == nil {
res.FlagSet = append(res.FlagSet, flag_incorrect_voucher)
return res, nil
}
// Store the active swap from data
if err := store.UpdateSwapFromVoucherData(ctx, userStore, sessionId, metadata); err != nil {
logg.ErrorCtxf(ctx, "failed on UpdateSwapFromVoucherData", "error", err)
return res, err
}
res.FlagReset = append(res.FlagReset, flag_incorrect_voucher)
activePoolAddress, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_ADDRESS)
if err != nil {
logg.ErrorCtxf(ctx, "failed to read activePoolAddress entry with", "key", storedb.DATA_ACTIVE_POOL_ADDRESS, "error", err)
return res, err
}
// 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 := h.prefixDb.Put(ctx, []byte(storedb.ToBytes(key)), []byte(value)); err != nil {
return res, nil
}
}
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
}
metadata, err := store.GetSwapToVoucherData(ctx, h.prefixDb, 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
}
userStore := h.userdataStore
// 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

@ -124,6 +124,12 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountService)
ls.DbRs.AddLocalFunc("set_back", appHandlers.SetBack)
ls.DbRs.AddLocalFunc("show_blocked_account", appHandlers.ShowBlockedAccount)
ls.DbRs.AddLocalFunc("clear_temporary_value", appHandlers.ClearTemporaryValue)
ls.DbRs.AddLocalFunc("get_pools", appHandlers.GetPools)
ls.DbRs.AddLocalFunc("swap_from_list", appHandlers.LoadSwapFromList)
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

@ -30,3 +30,6 @@ 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."

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

@ -9,14 +9,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 pool_swap 2
INCMP my_vouchers 3
INCMP my_account 4
INCMP help 5
INCMP quit 9
INCMP . *

View File

@ -0,0 +1,2 @@
Enter number or pool symbol to swap from:
{{.get_pools}}

View File

@ -0,0 +1,8 @@
LOAD get_pools 100
MAP get_pools
MOUT back 0
MOUT quit 99
HALT
INCMP _ 0
INCMP quit 99
INCMP swap_from_list *

View File

@ -0,0 +1,2 @@
Chagua bwawa la sarafu la kubadilishana:
{{.get_pools}}

View File

@ -29,4 +29,5 @@ flag,flag_location_set,35,this is set when the location of the profile is set
flag,flag_offerings_set,36,this is set when the offerings of the profile is set
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_incorrect_pool,39,this is set when the user selects an invalid pool
flag,flag_low_swap_amount,40,this is set when the swap max limit is less than 0.1

1 flag flag_language_set 8 checks whether the user has set their prefered language
29 flag flag_offerings_set 36 this is set when the offerings of the profile is set
30 flag flag_back_set 37 this is set when it is a back navigation
31 flag flag_account_blocked 38 this is set when an account has been blocked after the allowed incorrect PIN attempts have been exceeded
32 flag flag_incorrect_pool 39 this is set when the user selects an invalid pool
33 flag flag_low_swap_amount 40 this is set when the swap max limit is less than 0.1

View File

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

View File

@ -0,0 +1,7 @@
LOAD swap_from_list 0
CATCH _ flag_incorrect_pool 1
MAP swap_from_list
MOUT back 0
HALT
INCMP _ 0
INCMP swap_to_list *

View File

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

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,11 @@
LOAD swap_to_list 0
CATCH _ flag_incorrect_voucher 1
MAP swap_to_list
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

@ -63,6 +63,24 @@ const (
DATA_INITIAL_LANGUAGE_CODE
//Fully qualified account alias string
DATA_ACCOUNT_ALIAS
// 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 (
@ -101,6 +119,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, db storedb.PrefixDb, 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 := db.Get(ctx, storedb.ToBytes(key))
if err != nil {
return nil, fmt.Errorf("failed to get prefix key %x: %v", storedb.ToBytes(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
}

207
store/swap.go Normal file
View File

@ -0,0 +1,207 @@
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_SWAP_FROM_SYM,
"ActiveSwapFromDecimal": storedb.DATA_ACTIVE_SWAP_FROM_DECIMAL,
"ActiveSwapFromAddress": storedb.DATA_ACTIVE_SWAP_FROM_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_SWAP_FROM_DECIMAL,
"ActivePoolAddress": storedb.DATA_ACTIVE_POOL_ADDRESS,
"ActiveSwapFromAddress": storedb.DATA_ACTIVE_SWAP_FROM_ADDRESS,
"ActiveSwapFromSym": storedb.DATA_ACTIVE_SWAP_FROM_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, db storedb.PrefixDb, 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 := db.Get(ctx, storedb.ToBytes(key))
if err != nil {
return nil, fmt.Errorf("failed to get prefix key %x: %v", storedb.ToBytes(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
}
// UpdateSwapFromVoucherData updates the active swap to voucher data in the DataStore.
func UpdateSwapFromVoucherData(ctx context.Context, store DataStore, sessionId string, data *dataserviceapi.TokenHoldings) error {
logg.TraceCtxf(ctx, "dtal", "data", data)
// Active swap from voucher data entries
activeEntries := map[storedb.DataTyp][]byte{
storedb.DATA_ACTIVE_SWAP_FROM_SYM: []byte(data.TokenSymbol),
storedb.DATA_ACTIVE_SWAP_FROM_DECIMAL: []byte(data.TokenDecimals),
storedb.DATA_ACTIVE_SWAP_FROM_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
}
// GetSwapToVoucherData retrieves and matches voucher data
func GetSwapToVoucherData(ctx context.Context, db storedb.PrefixDb, 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 := db.Get(ctx, storedb.ToBytes(key))
if err != nil {
return nil, fmt.Errorf("failed to get prefix key %x: %v", storedb.ToBytes(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
}

206
store/swap_test.go Normal file
View File

@ -0,0 +1,206 @@
package store
import (
"context"
"testing"
visedb "git.defalsify.org/vise.git/db"
memdb "git.defalsify.org/vise.git/db/mem"
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
"github.com/alecthomas/assert/v2"
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
"github.com/stretchr/testify/require"
)
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) {
ctx := context.Background()
db := memdb.NewMemDb()
err := db.Connect(ctx, "")
if err != nil {
t.Fatal(err)
}
prefix := storedb.ToBytes(visedb.DATATYPE_USERDATA)
spdb := storedb.NewSubPrefixDb(db, prefix)
// 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 {
err = spdb.Put(ctx, []byte(storedb.ToBytes(key)), []byte(value))
if err != nil {
t.Fatal(err)
}
}
result, err := GetSwapFromVoucherData(ctx, spdb, "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 TestUpdateSwapFromVoucherData(t *testing.T) {
ctx, store := InitializeTestDb(t)
sessionId := "session123"
// New swap from voucher data
newData := &dataserviceapi.TokenHoldings{
TokenSymbol: "AMANI",
TokenDecimals: "6",
ContractAddress: "0xc7B78Ac9ACB9E025C8234621FC515bC58179dEAe",
}
// Old temporary data
tempData := &dataserviceapi.TokenHoldings{
TokenSymbol: "OLD",
TokenDecimals: "8",
ContractAddress: "0xold",
}
require.NoError(t, StoreTemporaryVoucher(ctx, store, sessionId, tempData))
// Execute update
err := UpdateSwapFromVoucherData(ctx, store, sessionId, newData)
require.NoError(t, err)
// Verify active swap from data was stored correctly
activeEntries := map[storedb.DataTyp][]byte{
storedb.DATA_ACTIVE_SWAP_FROM_SYM: []byte(newData.TokenSymbol),
storedb.DATA_ACTIVE_SWAP_FROM_DECIMAL: []byte(newData.TokenDecimals),
storedb.DATA_ACTIVE_SWAP_FROM_ADDRESS: []byte(newData.ContractAddress),
}
for key, expectedValue := range activeEntries {
storedValue, err := store.ReadEntry(ctx, sessionId, key)
require.NoError(t, err)
require.Equal(t, expectedValue, storedValue, "Active swap from data mismatch for key %v", key)
}
}
func TestGetSwapToVoucherData(t *testing.T) {
ctx := context.Background()
db := memdb.NewMemDb()
err := db.Connect(ctx, "")
if err != nil {
t.Fatal(err)
}
prefix := storedb.ToBytes(visedb.DATATYPE_USERDATA)
spdb := storedb.NewSubPrefixDb(db, prefix)
// 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 {
err = spdb.Put(ctx, []byte(storedb.ToBytes(key)), []byte(value))
if err != nil {
t.Fatal(err)
}
}
result, err := GetSwapToVoucherData(ctx, spdb, "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