WIP: alfred/pool-swap #33
4
go.mod
4
go.mod
@ -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
22
go.sum
@ -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=
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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."
|
||||
|
1
services/registration/low_swap_amount
Normal file
1
services/registration/low_swap_amount
Normal file
@ -0,0 +1 @@
|
||||
Available amount {{.swap_max_limit}} is too low, please try again:
|
6
services/registration/low_swap_amount.vis
Normal file
6
services/registration/low_swap_amount.vis
Normal file
@ -0,0 +1,6 @@
|
||||
MAP swap_max_limit
|
||||
MOUT retry 1
|
||||
MOUT quit 9
|
||||
HALT
|
||||
INCMP _ 1
|
||||
INCMP quit 9
|
1
services/registration/low_swap_amount_swa
Normal file
1
services/registration/low_swap_amount_swa
Normal file
@ -0,0 +1 @@
|
||||
Kiasi kinachopatikana {{.swap_max_limit}} ni cha chini sana, tafadhali jaribu tena:
|
@ -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 . *
|
||||
|
2
services/registration/pool_swap
Normal file
2
services/registration/pool_swap
Normal file
@ -0,0 +1,2 @@
|
||||
Enter number or pool symbol to swap from:
|
||||
{{.get_pools}}
|
8
services/registration/pool_swap.vis
Normal file
8
services/registration/pool_swap.vis
Normal 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 *
|
2
services/registration/pool_swap_swa
Normal file
2
services/registration/pool_swap_swa
Normal file
@ -0,0 +1,2 @@
|
||||
Chagua bwawa la sarafu la kubadilishana:
|
||||
{{.get_pools}}
|
@ -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
|
||||
|
|
2
services/registration/swap_from_list
Normal file
2
services/registration/swap_from_list
Normal file
@ -0,0 +1,2 @@
|
||||
Select number or symbol to swap FROM:
|
||||
{{.swap_from_list}}
|
7
services/registration/swap_from_list.vis
Normal file
7
services/registration/swap_from_list.vis
Normal 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 *
|
2
services/registration/swap_from_list_swa
Normal file
2
services/registration/swap_from_list_swa
Normal file
@ -0,0 +1,2 @@
|
||||
Chagua nambari au ishara ya sarafu kubadilisha KUTOKA:
|
||||
{{.swap_from_list}}
|
3
services/registration/swap_initiated.vis
Normal file
3
services/registration/swap_initiated.vis
Normal file
@ -0,0 +1,3 @@
|
||||
LOAD reset_incorrect 6
|
||||
LOAD initiate_swap 0
|
||||
HALT
|
1
services/registration/swap_limit
Normal file
1
services/registration/swap_limit
Normal file
@ -0,0 +1 @@
|
||||
{{.swap_max_limit}}
|
6
services/registration/swap_limit.vis
Normal file
6
services/registration/swap_limit.vis
Normal file
@ -0,0 +1,6 @@
|
||||
RELOAD swap_max_limit
|
||||
MAP swap_max_limit
|
||||
MOUT back 0
|
||||
HALT
|
||||
INCMP _ 0
|
||||
INCMP swap_preview *
|
1
services/registration/swap_limit_swa
Normal file
1
services/registration/swap_limit_swa
Normal file
@ -0,0 +1 @@
|
||||
{{.swap_max_limit}}
|
1
services/registration/swap_menu
Normal file
1
services/registration/swap_menu
Normal file
@ -0,0 +1 @@
|
||||
Swap
|
3
services/registration/swap_preview
Normal file
3
services/registration/swap_preview
Normal file
@ -0,0 +1,3 @@
|
||||
{{.swap_preview}}
|
||||
|
||||
Please enter your PIN to confirm:
|
12
services/registration/swap_preview.vis
Normal file
12
services/registration/swap_preview.vis
Normal 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 *
|
3
services/registration/swap_preview_swa
Normal file
3
services/registration/swap_preview_swa
Normal file
@ -0,0 +1,3 @@
|
||||
{{.swap_preview}}
|
||||
|
||||
Tafadhali weka PIN yako kudhibitisha:
|
2
services/registration/swap_to_list
Normal file
2
services/registration/swap_to_list
Normal file
@ -0,0 +1,2 @@
|
||||
Select number or symbol to swap TO:
|
||||
{{.swap_to_list}}
|
11
services/registration/swap_to_list.vis
Normal file
11
services/registration/swap_to_list.vis
Normal 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 *
|
2
services/registration/swap_to_list_swa
Normal file
2
services/registration/swap_to_list_swa
Normal file
@ -0,0 +1,2 @@
|
||||
Chagua nambari au ishara ya sarafu kubadilisha KWENDA:
|
||||
{{.swap_to_list}}
|
@ -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
93
store/pools.go
Normal 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
207
store/swap.go
Normal 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
206
store/swap_test.go
Normal 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)
|
||||
}
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user