Compare commits
19 Commits
ff26ccc545
...
a48170321c
Author | SHA1 | Date | |
---|---|---|---|
|
a48170321c | ||
|
1e638238ed | ||
|
4e81e2d869 | ||
a31cac4e50 | |||
8b399781e8 | |||
caafe495be | |||
66b34eaea4 | |||
adaa0c63ef | |||
7241cdbfcb | |||
0480c02633 | |||
03c32fd265 | |||
2c361e5b96 | |||
131f3bcf46 | |||
520f5abdcd | |||
5d294b663c | |||
dc782d87a8 | |||
ddae746b9d | |||
6c3ff0e9db | |||
3ef64271e7 |
@ -29,6 +29,10 @@ const (
|
|||||||
DATA_TEMPORARY_BAL
|
DATA_TEMPORARY_BAL
|
||||||
DATA_ACTIVE_BAL
|
DATA_ACTIVE_BAL
|
||||||
DATA_PUBLIC_KEY_REVERSE
|
DATA_PUBLIC_KEY_REVERSE
|
||||||
|
DATA_TEMPORARY_DECIMAL
|
||||||
|
DATA_ACTIVE_DECIMAL
|
||||||
|
DATA_TEMPORARY_ADDRESS
|
||||||
|
DATA_ACTIVE_ADDRESS
|
||||||
)
|
)
|
||||||
|
|
||||||
func typToBytes(typ DataTyp) []byte {
|
func typToBytes(typ DataTyp) []byte {
|
||||||
|
@ -1,18 +1,67 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import "git.grassecon.net/urdt/ussd/initializers"
|
import (
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"git.grassecon.net/urdt/ussd/initializers"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
createAccountPath = "/api/v2/account/create"
|
||||||
|
trackStatusPath = "/api/track"
|
||||||
|
balancePathPrefix = "/api/account"
|
||||||
|
trackPath = "/api/v2/account/status"
|
||||||
|
voucherHoldingsPathPrefix = "/api/v1/holdings"
|
||||||
|
voucherTransfersPathPrefix = "/api/v1/transfers/last10"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
custodialURLBase string
|
||||||
|
dataURLBase string
|
||||||
|
CustodialAPIKey string
|
||||||
|
DataAPIKey string
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
CreateAccountURL string
|
CreateAccountURL string
|
||||||
TrackStatusURL string
|
TrackStatusURL string
|
||||||
BalanceURL string
|
BalanceURL string
|
||||||
TrackURL string
|
TrackURL string
|
||||||
|
VoucherHoldingsURL string
|
||||||
|
VoucherTransfersURL string
|
||||||
)
|
)
|
||||||
|
|
||||||
// LoadConfig initializes the configuration values after environment variables are loaded.
|
func setBase() error {
|
||||||
func LoadConfig() {
|
var err error
|
||||||
CreateAccountURL = initializers.GetEnv("CREATE_ACCOUNT_URL", "http://localhost:5003/api/v2/account/create")
|
|
||||||
TrackStatusURL = initializers.GetEnv("TRACK_STATUS_URL", "https://custodial.sarafu.africa/api/track/")
|
custodialURLBase = initializers.GetEnv("CUSTODIAL_URL_BASE", "http://localhost:5003")
|
||||||
BalanceURL = initializers.GetEnv("BALANCE_URL", "https://custodial.sarafu.africa/api/account/status/")
|
dataURLBase = initializers.GetEnv("DATA_URL_BASE", "http://localhost:5006")
|
||||||
TrackURL = initializers.GetEnv("TRACK_URL", "http://localhost:5003/api/v2/account/status")
|
CustodialAPIKey = initializers.GetEnv("CUSTODIAL_API_KEY", "xd")
|
||||||
|
DataAPIKey = initializers.GetEnv("DATA_API_KEY", "xd")
|
||||||
|
|
||||||
|
_, err = url.JoinPath(custodialURLBase, "/foo")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = url.JoinPath(dataURLBase, "/bar")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadConfig initializes the configuration values after environment variables are loaded.
|
||||||
|
func LoadConfig() error {
|
||||||
|
err := setBase()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
CreateAccountURL, _ = url.JoinPath(custodialURLBase, createAccountPath)
|
||||||
|
TrackStatusURL, _ = url.JoinPath(custodialURLBase, trackStatusPath)
|
||||||
|
BalanceURL, _ = url.JoinPath(custodialURLBase, balancePathPrefix)
|
||||||
|
TrackURL, _ = url.JoinPath(custodialURLBase, trackPath)
|
||||||
|
VoucherHoldingsURL, _ = url.JoinPath(dataURLBase, voucherHoldingsPathPrefix)
|
||||||
|
VoucherTransfersURL, _ = url.JoinPath(dataURLBase, voucherTransfersPathPrefix)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
2
go.mod
2
go.mod
@ -12,6 +12,8 @@ require (
|
|||||||
gopkg.in/leonelquinteros/gotext.v1 v1.3.1
|
gopkg.in/leonelquinteros/gotext.v1 v1.3.1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require github.com/grassrootseconomics/ussd-data-service v0.0.0-20241003123429-4904b4438a3a // indirect
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
|
2
go.sum
2
go.sum
@ -18,6 +18,8 @@ github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1
|
|||||||
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
github.com/grassrootseconomics/eth-custodial v1.3.0-beta h1:twrMBhl89GqDUL9PlkzQxMP/6OST1BByrNDj+rqXDmU=
|
github.com/grassrootseconomics/eth-custodial v1.3.0-beta h1:twrMBhl89GqDUL9PlkzQxMP/6OST1BByrNDj+rqXDmU=
|
||||||
github.com/grassrootseconomics/eth-custodial v1.3.0-beta/go.mod h1:7uhRcdnJplX4t6GKCEFkbeDhhjlcaGJeJqevbcvGLZo=
|
github.com/grassrootseconomics/eth-custodial v1.3.0-beta/go.mod h1:7uhRcdnJplX4t6GKCEFkbeDhhjlcaGJeJqevbcvGLZo=
|
||||||
|
github.com/grassrootseconomics/ussd-data-service v0.0.0-20241003123429-4904b4438a3a h1:q/YH7nE2j8epNmFnTu0tU1vwtCxtQ6nH+d7hRVV5krU=
|
||||||
|
github.com/grassrootseconomics/ussd-data-service v0.0.0-20241003123429-4904b4438a3a/go.mod h1:hdKaKwqiW6/kphK4j/BhmuRlZDLo1+DYo3gYw5O0siw=
|
||||||
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 h1:U4kkNYryi/qfbBF8gh7Vsbuz+cVmhf5kt6pE9bYYyLo=
|
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/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=
|
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||||
|
@ -66,6 +66,7 @@ type Handlers struct {
|
|||||||
userdataStore common.DataStore
|
userdataStore common.DataStore
|
||||||
flagManager *asm.FlagParser
|
flagManager *asm.FlagParser
|
||||||
accountService remote.AccountServiceInterface
|
accountService remote.AccountServiceInterface
|
||||||
|
prefixDb storage.PrefixDb
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, accountService remote.AccountServiceInterface) (*Handlers, error) {
|
func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, accountService remote.AccountServiceInterface) (*Handlers, error) {
|
||||||
@ -75,10 +76,14 @@ func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, accountService r
|
|||||||
userDb := &common.UserDataStore{
|
userDb := &common.UserDataStore{
|
||||||
Db: userdataStore,
|
Db: userdataStore,
|
||||||
}
|
}
|
||||||
|
// Instantiate the SubPrefixDb with "vouchers" prefix
|
||||||
|
prefixDb := storage.NewSubPrefixDb(userdataStore, []byte("vouchers"))
|
||||||
|
|
||||||
h := &Handlers{
|
h := &Handlers{
|
||||||
userdataStore: userDb,
|
userdataStore: userDb,
|
||||||
flagManager: appFlags,
|
flagManager: appFlags,
|
||||||
accountService: accountService,
|
accountService: accountService,
|
||||||
|
prefixDb: prefixDb,
|
||||||
}
|
}
|
||||||
return h, nil
|
return h, nil
|
||||||
}
|
}
|
||||||
@ -144,12 +149,12 @@ func (h *Handlers) SetLanguage(ctx context.Context, sym string, input []byte) (r
|
|||||||
|
|
||||||
func (h *Handlers) createAccountNoExist(ctx context.Context, sessionId string, res *resource.Result) error {
|
func (h *Handlers) createAccountNoExist(ctx context.Context, sessionId string, res *resource.Result) error {
|
||||||
flag_account_created, _ := h.flagManager.GetFlag("flag_account_created")
|
flag_account_created, _ := h.flagManager.GetFlag("flag_account_created")
|
||||||
okResponse, err := h.accountService.CreateAccount(ctx)
|
r, err := h.accountService.CreateAccount(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
trackingId := okResponse.Result["trackingId"].(string)
|
trackingId := r.TrackingId
|
||||||
publicKey := okResponse.Result["publicKey"].(string)
|
publicKey := r.PublicKey
|
||||||
|
|
||||||
data := map[common.DataTyp]string{
|
data := map[common.DataTyp]string{
|
||||||
common.DATA_TRACKING_ID: trackingId,
|
common.DATA_TRACKING_ID: trackingId,
|
||||||
@ -677,6 +682,7 @@ func (h *Handlers) CheckBalance(ctx context.Context, sym string, input []byte) (
|
|||||||
|
|
||||||
func (h *Handlers) FetchCustodialBalances(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
func (h *Handlers) FetchCustodialBalances(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
var res resource.Result
|
var res resource.Result
|
||||||
|
|
||||||
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
|
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
|
||||||
|
|
||||||
sessionId, ok := ctx.Value("SessionId").(string)
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
@ -694,14 +700,13 @@ func (h *Handlers) FetchCustodialBalances(ctx context.Context, sym string, input
|
|||||||
|
|
||||||
balanceResponse, err := h.accountService.CheckBalance(ctx, string(publicKey))
|
balanceResponse, err := h.accountService.CheckBalance(ctx, string(publicKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
if !balanceResponse.Ok {
|
|
||||||
res.FlagSet = append(res.FlagSet, flag_api_error)
|
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
res.FlagReset = append(res.FlagReset, flag_api_error)
|
res.FlagReset = append(res.FlagReset, flag_api_error)
|
||||||
balance := balanceResponse.Result.Balance
|
|
||||||
|
//balance := balanceResponse.Result.Balance
|
||||||
|
balance := balanceResponse.Balance
|
||||||
|
|
||||||
switch balanceType {
|
switch balanceType {
|
||||||
case "my":
|
case "my":
|
||||||
@ -1051,23 +1056,23 @@ func (h *Handlers) SetDefaultVoucher(ctx context.Context, sym string, input []by
|
|||||||
if db.IsNotFound(err) {
|
if db.IsNotFound(err) {
|
||||||
publicKey, err := store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY)
|
publicKey, err := store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, nil
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch vouchers from the API using the public key
|
// Fetch vouchers from the API using the public key
|
||||||
vouchersResp, err := h.accountService.FetchVouchers(ctx, string(publicKey))
|
vouchersResp, err := h.accountService.FetchVouchers(ctx, string(publicKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, nil
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return if there is no voucher
|
// Return if there is no voucher
|
||||||
if len(vouchersResp.Result.Holdings) == 0 {
|
if len(vouchersResp) == 0 {
|
||||||
res.FlagSet = append(res.FlagSet, flag_no_active_voucher)
|
res.FlagSet = append(res.FlagSet, flag_no_active_voucher)
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use only the first voucher
|
// Use only the first voucher
|
||||||
firstVoucher := vouchersResp.Result.Holdings[0]
|
firstVoucher := vouchersResp[0]
|
||||||
defaultSym := firstVoucher.TokenSymbol
|
defaultSym := firstVoucher.TokenSymbol
|
||||||
defaultBal := firstVoucher.Balance
|
defaultBal := firstVoucher.Balance
|
||||||
|
|
||||||
@ -1114,54 +1119,33 @@ func (h *Handlers) CheckVouchers(ctx context.Context, sym string, input []byte)
|
|||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// process voucher data
|
data := utils.ProcessVouchers(vouchersResp)
|
||||||
voucherSymbolList, voucherBalanceList := ProcessVouchers(vouchersResp.Result.Holdings)
|
|
||||||
|
|
||||||
prefixdb := storage.NewSubPrefixDb(store, []byte("vouchers"))
|
// Store all voucher data
|
||||||
err = prefixdb.Put(ctx, []byte("sym"), []byte(voucherSymbolList))
|
dataMap := map[string]string{
|
||||||
if err != nil {
|
"sym": data.Symbols,
|
||||||
return res, nil
|
"bal": data.Balances,
|
||||||
|
"deci": data.Decimals,
|
||||||
|
"addr": data.Addresses,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = prefixdb.Put(ctx, []byte("bal"), []byte(voucherBalanceList))
|
for key, value := range dataMap {
|
||||||
if err != nil {
|
if err := h.prefixDb.Put(ctx, []byte(key), []byte(value)); err != nil {
|
||||||
return res, nil
|
return res, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProcessVouchers formats the holdings into symbol and balance lists.
|
|
||||||
func ProcessVouchers(holdings []struct {
|
|
||||||
ContractAddress string `json:"contractAddress"`
|
|
||||||
TokenSymbol string `json:"tokenSymbol"`
|
|
||||||
TokenDecimals string `json:"tokenDecimals"`
|
|
||||||
Balance string `json:"balance"`
|
|
||||||
}) (string, string) {
|
|
||||||
var numberedSymbols, numberedBalances []string
|
|
||||||
|
|
||||||
for i, voucher := range holdings {
|
|
||||||
numberedSymbols = append(numberedSymbols, fmt.Sprintf("%d:%s", i+1, voucher.TokenSymbol))
|
|
||||||
numberedBalances = append(numberedBalances, fmt.Sprintf("%d:%s", i+1, voucher.Balance))
|
|
||||||
}
|
|
||||||
|
|
||||||
voucherSymbolList := strings.Join(numberedSymbols, "\n")
|
|
||||||
voucherBalanceList := strings.Join(numberedBalances, "\n")
|
|
||||||
|
|
||||||
return voucherSymbolList, voucherBalanceList
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVoucherList fetches the list of vouchers and formats them
|
// GetVoucherList fetches the list of vouchers and formats them
|
||||||
func (h *Handlers) GetVoucherList(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
func (h *Handlers) GetVoucherList(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
var res resource.Result
|
var res resource.Result
|
||||||
|
|
||||||
// Read vouchers from the store
|
// Read vouchers from the store
|
||||||
store := h.userdataStore
|
voucherData, err := h.prefixDb.Get(ctx, []byte("sym"))
|
||||||
prefixdb := storage.NewSubPrefixDb(store, []byte("vouchers"))
|
|
||||||
|
|
||||||
voucherData, err := prefixdb.Get(ctx, []byte("sym"))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, nil
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
res.Content = string(voucherData)
|
res.Content = string(voucherData)
|
||||||
@ -1172,8 +1156,6 @@ func (h *Handlers) GetVoucherList(ctx context.Context, sym string, input []byte)
|
|||||||
// ViewVoucher retrieves the token holding and balance from the subprefixDB
|
// ViewVoucher retrieves the token holding and balance from the subprefixDB
|
||||||
func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
var res resource.Result
|
var res resource.Result
|
||||||
store := h.userdataStore
|
|
||||||
|
|
||||||
sessionId, ok := ctx.Value("SessionId").(string)
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return res, fmt.Errorf("missing session")
|
return res, fmt.Errorf("missing session")
|
||||||
@ -1182,126 +1164,52 @@ func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (r
|
|||||||
flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher")
|
flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher")
|
||||||
|
|
||||||
inputStr := string(input)
|
inputStr := string(input)
|
||||||
|
|
||||||
if inputStr == "0" || inputStr == "99" {
|
if inputStr == "0" || inputStr == "99" {
|
||||||
res.FlagReset = append(res.FlagReset, flag_incorrect_voucher)
|
res.FlagReset = append(res.FlagReset, flag_incorrect_voucher)
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
prefixdb := storage.NewSubPrefixDb(store, []byte("vouchers"))
|
metadata, err := utils.GetVoucherData(ctx, h.prefixDb, inputStr)
|
||||||
|
|
||||||
// Retrieve the voucher symbol list
|
|
||||||
voucherSymbolList, err := prefixdb.Get(ctx, []byte("sym"))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, fmt.Errorf("failed to retrieve voucher symbol list: %v", err)
|
return res, fmt.Errorf("failed to retrieve voucher data: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve the voucher balance list
|
if metadata == nil {
|
||||||
voucherBalanceList, err := prefixdb.Get(ctx, []byte("bal"))
|
|
||||||
if err != nil {
|
|
||||||
return res, fmt.Errorf("failed to retrieve voucher balance list: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// match the voucher symbol and balance with the input
|
|
||||||
matchedSymbol, matchedBalance := MatchVoucher(inputStr, string(voucherSymbolList), string(voucherBalanceList))
|
|
||||||
|
|
||||||
// If a match is found, write the temporary sym and balance
|
|
||||||
if matchedSymbol != "" && matchedBalance != "" {
|
|
||||||
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_SYM, []byte(matchedSymbol))
|
|
||||||
if err != nil {
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_BAL, []byte(matchedBalance))
|
|
||||||
if err != nil {
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
res.FlagReset = append(res.FlagReset, flag_incorrect_voucher)
|
|
||||||
res.Content = fmt.Sprintf("%s\n%s", matchedSymbol, matchedBalance)
|
|
||||||
} else {
|
|
||||||
res.FlagSet = append(res.FlagSet, flag_incorrect_voucher)
|
res.FlagSet = append(res.FlagSet, flag_incorrect_voucher)
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := utils.StoreTemporaryVoucher(ctx, h.userdataStore, sessionId, metadata); err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_incorrect_voucher)
|
||||||
|
res.Content = fmt.Sprintf("%s\n%s", metadata.TokenSymbol, metadata.Balance)
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchVoucher finds the matching voucher symbol and balance based on the input.
|
// SetVoucher retrieves the temp voucher data and sets it as the active data
|
||||||
func MatchVoucher(inputStr string, voucherSymbols, voucherBalances string) (string, string) {
|
|
||||||
// Split the lists into slices for processing
|
|
||||||
symbols := strings.Split(voucherSymbols, "\n")
|
|
||||||
balances := strings.Split(voucherBalances, "\n")
|
|
||||||
|
|
||||||
var matchedSymbol, matchedBalance string
|
|
||||||
|
|
||||||
for i, symbol := range symbols {
|
|
||||||
symbolParts := strings.SplitN(symbol, ":", 2)
|
|
||||||
if len(symbolParts) != 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
voucherNum := symbolParts[0]
|
|
||||||
voucherSymbol := symbolParts[1]
|
|
||||||
|
|
||||||
// Check if input matches either the number or the symbol
|
|
||||||
if inputStr == voucherNum || strings.EqualFold(inputStr, voucherSymbol) {
|
|
||||||
matchedSymbol = voucherSymbol
|
|
||||||
// Ensure there's a corresponding balance
|
|
||||||
if i < len(balances) {
|
|
||||||
matchedBalance = strings.SplitN(balances[i], ":", 2)[1]
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return matchedSymbol, matchedBalance
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetVoucher retrieves the temporary sym and balance,
|
|
||||||
// sets them as the active data and
|
|
||||||
// clears the temporary data
|
|
||||||
func (h *Handlers) SetVoucher(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
func (h *Handlers) SetVoucher(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
var res resource.Result
|
var res resource.Result
|
||||||
var err error
|
|
||||||
store := h.userdataStore
|
|
||||||
|
|
||||||
sessionId, ok := ctx.Value("SessionId").(string)
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return res, fmt.Errorf("missing session")
|
return res, fmt.Errorf("missing session")
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the current temporary symbol
|
|
||||||
temporarySym, err := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_SYM)
|
// Get temporary data
|
||||||
if err != nil {
|
tempData, err := utils.GetTemporaryVoucherData(ctx, h.userdataStore, sessionId)
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
// get the current temporary balance
|
|
||||||
temporaryBal, err := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_BAL)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the active symbol
|
// Set as active and clear temporary data
|
||||||
err = store.WriteEntry(ctx, sessionId, common.DATA_ACTIVE_SYM, []byte(temporarySym))
|
if err := utils.UpdateVoucherData(ctx, h.userdataStore, sessionId, tempData); err != nil {
|
||||||
if err != nil {
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
// set the active balance
|
|
||||||
err = store.WriteEntry(ctx, sessionId, common.DATA_ACTIVE_BAL, []byte(temporaryBal))
|
|
||||||
if err != nil {
|
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset the temporary symbol
|
res.Content = tempData.TokenSymbol
|
||||||
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_SYM, []byte(""))
|
|
||||||
if err != nil {
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
// reset the temporary balance
|
|
||||||
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_BAL, []byte(""))
|
|
||||||
if err != nil {
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
res.Content = string(temporarySym)
|
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,8 @@ import (
|
|||||||
"github.com/grassrootseconomics/eth-custodial/pkg/api"
|
"github.com/grassrootseconomics/eth-custodial/pkg/api"
|
||||||
testdataloader "github.com/peteole/testdata-loader"
|
testdataloader "github.com/peteole/testdata-loader"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -86,18 +88,14 @@ func TestCreateAccount(t *testing.T) {
|
|||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
serverResponse *api.OKResponse
|
serverResponse *models.AccountResult
|
||||||
expectedResult resource.Result
|
expectedResult resource.Result
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Test account creation success",
|
name: "Test account creation success",
|
||||||
serverResponse: &api.OKResponse{
|
serverResponse: &models.AccountResult{
|
||||||
Ok: true,
|
TrackingId: "1234567890",
|
||||||
Description: "Account creation successed",
|
PublicKey: "0xD3adB33f",
|
||||||
Result: map[string]any{
|
|
||||||
"trackingId": "1234567890",
|
|
||||||
"publicKey": "0xD3adB33f",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
expectedResult: resource.Result{
|
expectedResult: resource.Result{
|
||||||
FlagSet: []uint32{flag_account_created},
|
FlagSet: []uint32{flag_account_created},
|
||||||
@ -117,9 +115,9 @@ func TestCreateAccount(t *testing.T) {
|
|||||||
flagManager: fm.parser,
|
flagManager: fm.parser,
|
||||||
}
|
}
|
||||||
|
|
||||||
publicKey := tt.serverResponse.Result["publicKey"].(string)
|
publicKey := tt.serverResponse.PublicKey
|
||||||
data := map[common.DataTyp]string{
|
data := map[common.DataTyp]string{
|
||||||
common.DATA_TRACKING_ID: tt.serverResponse.Result["trackingId"].(string),
|
common.DATA_TRACKING_ID: tt.serverResponse.TrackingId,
|
||||||
common.DATA_PUBLIC_KEY: publicKey,
|
common.DATA_PUBLIC_KEY: publicKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1053,7 +1051,7 @@ func TestCheckAccountStatus(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
input []byte
|
input []byte
|
||||||
serverResponse *api.OKResponse
|
serverResponse *api.OKResponse
|
||||||
response *models.TrackStatusResponse
|
response *models.TrackStatusResult
|
||||||
expectedResult resource.Result
|
expectedResult resource.Result
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@ -1066,25 +1064,12 @@ func TestCheckAccountStatus(t *testing.T) {
|
|||||||
"active": true,
|
"active": true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
response: &models.TrackStatusResponse{
|
response: &models.TrackStatusResult {
|
||||||
Ok: true,
|
CreatedAt: time.Now(),
|
||||||
Result: struct {
|
Status: "SUCCESS",
|
||||||
Transaction struct {
|
TransferValue: json.Number("0.5"),
|
||||||
CreatedAt time.Time "json:\"createdAt\""
|
TxHash: "0x123abc456def",
|
||||||
Status string "json:\"status\""
|
TxType: "transfer",
|
||||||
TransferValue json.Number "json:\"transferValue\""
|
|
||||||
TxHash string "json:\"txHash\""
|
|
||||||
TxType string "json:\"txType\""
|
|
||||||
}
|
|
||||||
}{
|
|
||||||
Transaction: models.Transaction{
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
Status: "SUCCESS",
|
|
||||||
TransferValue: json.Number("0.5"),
|
|
||||||
TxHash: "0x123abc456def",
|
|
||||||
TxType: "transfer",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
expectedResult: resource.Result{
|
expectedResult: resource.Result{
|
||||||
FlagSet: []uint32{flag_account_success},
|
FlagSet: []uint32{flag_account_success},
|
||||||
@ -1094,25 +1079,12 @@ func TestCheckAccountStatus(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Test when the account is not yet on the sarafu network",
|
name: "Test when the account is not yet on the sarafu network",
|
||||||
input: []byte("TrackingId1234"),
|
input: []byte("TrackingId1234"),
|
||||||
response: &models.TrackStatusResponse{
|
response: &models.TrackStatusResult{
|
||||||
Ok: true,
|
CreatedAt: time.Now(),
|
||||||
Result: struct {
|
Status: "SUCCESS",
|
||||||
Transaction struct {
|
TransferValue: json.Number("0.5"),
|
||||||
CreatedAt time.Time "json:\"createdAt\""
|
TxHash: "0x123abc456def",
|
||||||
Status string "json:\"status\""
|
TxType: "transfer",
|
||||||
TransferValue json.Number "json:\"transferValue\""
|
|
||||||
TxHash string "json:\"txHash\""
|
|
||||||
TxType string "json:\"txType\""
|
|
||||||
}
|
|
||||||
}{
|
|
||||||
Transaction: models.Transaction{
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
Status: "SUCCESS",
|
|
||||||
TransferValue: json.Number("0.5"),
|
|
||||||
TxHash: "0x123abc456def",
|
|
||||||
TxType: "transfer",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
serverResponse: &api.OKResponse{
|
serverResponse: &api.OKResponse{
|
||||||
Ok: true,
|
Ok: true,
|
||||||
@ -1138,7 +1110,7 @@ func TestCheckAccountStatus(t *testing.T) {
|
|||||||
flagManager: fm.parser,
|
flagManager: fm.parser,
|
||||||
}
|
}
|
||||||
|
|
||||||
status := tt.response.Result.Transaction.Status
|
status := tt.response.Status
|
||||||
// Define expected interactions with the mock
|
// Define expected interactions with the mock
|
||||||
mockDataStore.On("ReadEntry", ctx, sessionId, common.DATA_PUBLIC_KEY).Return(tt.input, nil)
|
mockDataStore.On("ReadEntry", ctx, sessionId, common.DATA_PUBLIC_KEY).Return(tt.input, nil)
|
||||||
|
|
||||||
@ -1157,7 +1129,6 @@ func TestCheckAccountStatus(t *testing.T) {
|
|||||||
|
|
||||||
// Assert that expectations were met
|
// Assert that expectations were met
|
||||||
mockDataStore.AssertExpectations(t)
|
mockDataStore.AssertExpectations(t)
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1488,8 +1459,8 @@ func TestValidateAmount(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Test with invalid amount format",
|
name: "Test with invalid amount format",
|
||||||
input: []byte("0.02ms"),
|
input: []byte("0.02ms"),
|
||||||
activeBal: []byte("5"),
|
activeBal: []byte("5"),
|
||||||
expectedResult: resource.Result{
|
expectedResult: resource.Result{
|
||||||
FlagSet: []uint32{flag_invalid_amount},
|
FlagSet: []uint32{flag_invalid_amount},
|
||||||
@ -1818,40 +1789,6 @@ func TestConfirmPin(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSetVoucher(t *testing.T) {
|
|
||||||
mockDataStore := new(mocks.MockUserDataStore)
|
|
||||||
|
|
||||||
sessionId := "session123"
|
|
||||||
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
|
|
||||||
|
|
||||||
temporarySym := []byte("tempSym")
|
|
||||||
temporaryBal := []byte("tempBal")
|
|
||||||
|
|
||||||
// Set expectations for the mock data store
|
|
||||||
mockDataStore.On("ReadEntry", ctx, sessionId, common.DATA_TEMPORARY_SYM).Return(temporarySym, nil)
|
|
||||||
mockDataStore.On("ReadEntry", ctx, sessionId, common.DATA_TEMPORARY_BAL).Return(temporaryBal, nil)
|
|
||||||
mockDataStore.On("WriteEntry", ctx, sessionId, common.DATA_ACTIVE_SYM, temporarySym).Return(nil)
|
|
||||||
mockDataStore.On("WriteEntry", ctx, sessionId, common.DATA_ACTIVE_BAL, temporaryBal).Return(nil)
|
|
||||||
mockDataStore.On("WriteEntry", ctx, sessionId, common.DATA_TEMPORARY_SYM, []byte("")).Return(nil)
|
|
||||||
mockDataStore.On("WriteEntry", ctx, sessionId, common.DATA_TEMPORARY_BAL, []byte("")).Return(nil)
|
|
||||||
|
|
||||||
h := &Handlers{
|
|
||||||
userdataStore: mockDataStore,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call the method under test
|
|
||||||
res, err := h.SetVoucher(ctx, "someSym", []byte{})
|
|
||||||
|
|
||||||
// Assert that no errors occurred
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
// Assert that the result content is correct
|
|
||||||
assert.Equal(t, string(temporarySym), res.Content)
|
|
||||||
|
|
||||||
// Assert that expectations were met
|
|
||||||
mockDataStore.AssertExpectations(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFetchCustodialBalances(t *testing.T) {
|
func TestFetchCustodialBalances(t *testing.T) {
|
||||||
fm, err := NewFlagManager(flagsPath)
|
fm, err := NewFlagManager(flagsPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1866,36 +1803,14 @@ func TestFetchCustodialBalances(t *testing.T) {
|
|||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
balanceResonse *models.BalanceResponse
|
balanceResponse *models.BalanceResult
|
||||||
expectedResult resource.Result
|
expectedResult resource.Result
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Test when fetch custodial balances is not a success",
|
name: "Test when fetch custodial balances is not a success",
|
||||||
balanceResonse: &models.BalanceResponse{
|
balanceResponse: &models.BalanceResult{
|
||||||
Ok: false,
|
Balance: "0.003 CELO",
|
||||||
Result: struct {
|
Nonce: json.Number("0"),
|
||||||
Balance string `json:"balance"`
|
|
||||||
Nonce json.Number `json:"nonce"`
|
|
||||||
}{
|
|
||||||
Balance: "0.003 CELO",
|
|
||||||
Nonce: json.Number("0"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedResult: resource.Result{
|
|
||||||
FlagSet: []uint32{flag_api_error},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test when fetch custodial balances is a success",
|
|
||||||
balanceResonse: &models.BalanceResponse{
|
|
||||||
Ok: true,
|
|
||||||
Result: struct {
|
|
||||||
Balance string `json:"balance"`
|
|
||||||
Nonce json.Number `json:"nonce"`
|
|
||||||
}{
|
|
||||||
Balance: "0.003 CELO",
|
|
||||||
Nonce: json.Number("0"),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
expectedResult: resource.Result{
|
expectedResult: resource.Result{
|
||||||
FlagReset: []uint32{flag_api_error},
|
FlagReset: []uint32{flag_api_error},
|
||||||
@ -1919,7 +1834,7 @@ func TestFetchCustodialBalances(t *testing.T) {
|
|||||||
|
|
||||||
// Set up the expected behavior of the mock
|
// Set up the expected behavior of the mock
|
||||||
mockDataStore.On("ReadEntry", ctx, sessionId, common.DATA_PUBLIC_KEY).Return([]byte(publicKey), nil)
|
mockDataStore.On("ReadEntry", ctx, sessionId, common.DATA_PUBLIC_KEY).Return([]byte(publicKey), nil)
|
||||||
mockCreateAccountService.On("CheckBalance", string(publicKey)).Return(tt.balanceResonse, nil)
|
mockCreateAccountService.On("CheckBalance", string(publicKey)).Return(tt.balanceResponse, nil)
|
||||||
|
|
||||||
// Call the method
|
// Call the method
|
||||||
res, _ := h.FetchCustodialBalances(ctx, "fetch_custodial_balances", []byte(""))
|
res, _ := h.FetchCustodialBalances(ctx, "fetch_custodial_balances", []byte(""))
|
||||||
@ -1933,3 +1848,244 @@ func TestFetchCustodialBalances(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSetDefaultVoucher(t *testing.T) {
|
||||||
|
fm, err := NewFlagManager(flagsPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf(err.Error())
|
||||||
|
}
|
||||||
|
flag_no_active_voucher, err := fm.GetFlag("flag_no_active_voucher")
|
||||||
|
if err != nil {
|
||||||
|
t.Logf(err.Error())
|
||||||
|
}
|
||||||
|
// Define session ID and mock data
|
||||||
|
sessionId := "session123"
|
||||||
|
publicKey := "0X13242618721"
|
||||||
|
notFoundErr := db.ErrNotFound{}
|
||||||
|
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
vouchersResp []dataserviceapi.TokenHoldings
|
||||||
|
expectedResult resource.Result
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Test set default voucher when no active voucher exists",
|
||||||
|
vouchersResp: []dataserviceapi.TokenHoldings {
|
||||||
|
dataserviceapi.TokenHoldings{
|
||||||
|
ContractAddress: "0x123",
|
||||||
|
TokenSymbol: "TOKEN1",
|
||||||
|
TokenDecimals: "18",
|
||||||
|
Balance: "100",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedResult: resource.Result{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test no vouchers available",
|
||||||
|
vouchersResp: []dataserviceapi.TokenHoldings {
|
||||||
|
},
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagSet: []uint32{flag_no_active_voucher},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
mockDataStore := new(mocks.MockUserDataStore)
|
||||||
|
mockAccountService := new(mocks.MockAccountService)
|
||||||
|
|
||||||
|
h := &Handlers{
|
||||||
|
userdataStore: mockDataStore,
|
||||||
|
accountService: mockAccountService,
|
||||||
|
flagManager: fm.parser,
|
||||||
|
}
|
||||||
|
|
||||||
|
mockDataStore.On("ReadEntry", ctx, sessionId, common.DATA_ACTIVE_SYM).Return([]byte(""), notFoundErr)
|
||||||
|
mockDataStore.On("ReadEntry", ctx, sessionId, common.DATA_PUBLIC_KEY).Return([]byte(publicKey), nil)
|
||||||
|
|
||||||
|
mockAccountService.On("FetchVouchers", string(publicKey)).Return(tt.vouchersResp, nil)
|
||||||
|
|
||||||
|
if len(tt.vouchersResp) > 0 {
|
||||||
|
firstVoucher := tt.vouchersResp[0]
|
||||||
|
mockDataStore.On("WriteEntry", ctx, sessionId, common.DATA_ACTIVE_SYM, []byte(firstVoucher.TokenSymbol)).Return(nil)
|
||||||
|
mockDataStore.On("WriteEntry", ctx, sessionId, common.DATA_ACTIVE_BAL, []byte(firstVoucher.Balance)).Return(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := h.SetDefaultVoucher(ctx, "set_default_voucher", []byte("some-input"))
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, res, tt.expectedResult, "Expected result should be equal to the actual result")
|
||||||
|
|
||||||
|
mockDataStore.AssertExpectations(t)
|
||||||
|
mockAccountService.AssertExpectations(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckVouchers(t *testing.T) {
|
||||||
|
mockDataStore := new(mocks.MockUserDataStore)
|
||||||
|
mockAccountService := new(mocks.MockAccountService)
|
||||||
|
mockSubPrefixDb := new(mocks.MockSubPrefixDb)
|
||||||
|
|
||||||
|
sessionId := "session123"
|
||||||
|
publicKey := "0X13242618721"
|
||||||
|
|
||||||
|
h := &Handlers{
|
||||||
|
userdataStore: mockDataStore,
|
||||||
|
accountService: mockAccountService,
|
||||||
|
prefixDb: mockSubPrefixDb,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
|
||||||
|
|
||||||
|
mockDataStore.On("ReadEntry", ctx, sessionId, common.DATA_PUBLIC_KEY).Return([]byte(publicKey), nil)
|
||||||
|
|
||||||
|
mockVouchersResponse := []dataserviceapi.TokenHoldings{
|
||||||
|
{ContractAddress: "0xd4c288865Ce", TokenSymbol: "SRF", TokenDecimals: "6", Balance: "100"},
|
||||||
|
{ContractAddress: "0x41c188d63Qa", TokenSymbol: "MILO", TokenDecimals: "4", Balance: "200"},
|
||||||
|
}
|
||||||
|
|
||||||
|
mockAccountService.On("FetchVouchers", string(publicKey)).Return(mockVouchersResponse, nil)
|
||||||
|
|
||||||
|
mockSubPrefixDb.On("Put", ctx, []byte("sym"), []byte("1:SRF\n2:MILO")).Return(nil)
|
||||||
|
mockSubPrefixDb.On("Put", ctx, []byte("bal"), []byte("1:100\n2:200")).Return(nil)
|
||||||
|
mockSubPrefixDb.On("Put", ctx, []byte("deci"), []byte("1:6\n2:4")).Return(nil)
|
||||||
|
mockSubPrefixDb.On("Put", ctx, []byte("addr"), []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa")).Return(nil)
|
||||||
|
|
||||||
|
_, err := h.CheckVouchers(ctx, "check_vouchers", []byte(""))
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
mockDataStore.AssertExpectations(t)
|
||||||
|
mockAccountService.AssertExpectations(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetVoucherList(t *testing.T) {
|
||||||
|
mockSubPrefixDb := new(mocks.MockSubPrefixDb)
|
||||||
|
|
||||||
|
sessionId := "session123"
|
||||||
|
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
|
||||||
|
|
||||||
|
h := &Handlers{
|
||||||
|
prefixDb: mockSubPrefixDb,
|
||||||
|
}
|
||||||
|
|
||||||
|
mockSubPrefixDb.On("Get", ctx, []byte("sym")).Return([]byte("1:SRF\n2:MILO"), nil)
|
||||||
|
|
||||||
|
res, err := h.GetVoucherList(ctx, "", []byte(""))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Contains(t, res.Content, "1:SRF\n2:MILO")
|
||||||
|
|
||||||
|
mockSubPrefixDb.AssertExpectations(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestViewVoucher(t *testing.T) {
|
||||||
|
fm, err := NewFlagManager(flagsPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf(err.Error())
|
||||||
|
}
|
||||||
|
mockDataStore := new(mocks.MockUserDataStore)
|
||||||
|
mockSubPrefixDb := new(mocks.MockSubPrefixDb)
|
||||||
|
|
||||||
|
sessionId := "session123"
|
||||||
|
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
|
||||||
|
|
||||||
|
h := &Handlers{
|
||||||
|
userdataStore: mockDataStore,
|
||||||
|
flagManager: fm.parser,
|
||||||
|
prefixDb: mockSubPrefixDb,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define mock voucher data
|
||||||
|
mockVoucherData := map[string]string{
|
||||||
|
"sym": "1:SRF",
|
||||||
|
"bal": "1:100",
|
||||||
|
"deci": "1:6",
|
||||||
|
"addr": "1:0xd4c288865Ce",
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range mockVoucherData {
|
||||||
|
mockSubPrefixDb.On("Get", ctx, []byte(key)).Return([]byte(value), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up expectations for mockDataStore
|
||||||
|
expectedData := map[common.DataTyp]string{
|
||||||
|
common.DATA_TEMPORARY_SYM: "SRF",
|
||||||
|
common.DATA_TEMPORARY_BAL: "100",
|
||||||
|
common.DATA_TEMPORARY_DECIMAL: "6",
|
||||||
|
common.DATA_TEMPORARY_ADDRESS: "0xd4c288865Ce",
|
||||||
|
}
|
||||||
|
|
||||||
|
for dataType, dataValue := range expectedData {
|
||||||
|
mockDataStore.On("WriteEntry", ctx, sessionId, dataType, []byte(dataValue)).Return(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := h.ViewVoucher(ctx, "view_voucher", []byte("1"))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Contains(t, res.Content, "SRF\n100")
|
||||||
|
|
||||||
|
mockDataStore.AssertExpectations(t)
|
||||||
|
mockSubPrefixDb.AssertExpectations(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetVoucher(t *testing.T) {
|
||||||
|
mockDataStore := new(mocks.MockUserDataStore)
|
||||||
|
|
||||||
|
sessionId := "session123"
|
||||||
|
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
|
||||||
|
|
||||||
|
// Define the temporary voucher data
|
||||||
|
tempData := &dataserviceapi.TokenHoldings{
|
||||||
|
TokenSymbol: "SRF",
|
||||||
|
Balance: "200",
|
||||||
|
TokenDecimals: "6",
|
||||||
|
ContractAddress: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define the expected active entries
|
||||||
|
activeEntries := map[common.DataTyp][]byte{
|
||||||
|
common.DATA_ACTIVE_SYM: []byte(tempData.TokenSymbol),
|
||||||
|
common.DATA_ACTIVE_BAL: []byte(tempData.Balance),
|
||||||
|
common.DATA_ACTIVE_DECIMAL: []byte(tempData.TokenDecimals),
|
||||||
|
common.DATA_ACTIVE_ADDRESS: []byte(tempData.ContractAddress),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define the temporary entries to be cleared
|
||||||
|
tempEntries := map[common.DataTyp][]byte{
|
||||||
|
common.DATA_TEMPORARY_SYM: []byte(""),
|
||||||
|
common.DATA_TEMPORARY_BAL: []byte(""),
|
||||||
|
common.DATA_TEMPORARY_DECIMAL: []byte(""),
|
||||||
|
common.DATA_TEMPORARY_ADDRESS: []byte(""),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mocking ReadEntry calls for temporary data retrieval
|
||||||
|
mockDataStore.On("ReadEntry", ctx, sessionId, common.DATA_TEMPORARY_SYM).Return([]byte(tempData.TokenSymbol), nil)
|
||||||
|
mockDataStore.On("ReadEntry", ctx, sessionId, common.DATA_TEMPORARY_BAL).Return([]byte(tempData.Balance), nil)
|
||||||
|
mockDataStore.On("ReadEntry", ctx, sessionId, common.DATA_TEMPORARY_DECIMAL).Return([]byte(tempData.TokenDecimals), nil)
|
||||||
|
mockDataStore.On("ReadEntry", ctx, sessionId, common.DATA_TEMPORARY_ADDRESS).Return([]byte(tempData.ContractAddress), nil)
|
||||||
|
|
||||||
|
// Mocking WriteEntry calls for setting active data
|
||||||
|
for key, value := range activeEntries {
|
||||||
|
mockDataStore.On("WriteEntry", ctx, sessionId, key, value).Return(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mocking WriteEntry calls for clearing temporary data
|
||||||
|
for key, value := range tempEntries {
|
||||||
|
mockDataStore.On("WriteEntry", ctx, sessionId, key, value).Return(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
h := &Handlers{
|
||||||
|
userdataStore: mockDataStore,
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := h.SetVoucher(ctx, "someSym", []byte{})
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, string(tempData.TokenSymbol), res.Content)
|
||||||
|
|
||||||
|
mockDataStore.AssertExpectations(t)
|
||||||
|
}
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
type AccountResponse struct {
|
type AccountResult struct {
|
||||||
Ok bool `json:"ok"`
|
PublicKey string `json:"publicKey"`
|
||||||
Description string `json:"description"` // Include the description field
|
TrackingId string `json:"trackingId"`
|
||||||
Result struct {
|
|
||||||
PublicKey string `json:"publicKey"`
|
|
||||||
TrackingId string `json:"trackingId"`
|
|
||||||
} `json:"result"`
|
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,15 @@ package models
|
|||||||
import "encoding/json"
|
import "encoding/json"
|
||||||
|
|
||||||
|
|
||||||
type BalanceResponse struct {
|
//type BalanceResponse struct {
|
||||||
Ok bool `json:"ok"`
|
// Ok bool `json:"ok"`
|
||||||
Result struct {
|
// Result struct {
|
||||||
Balance string `json:"balance"`
|
// Balance string `json:"balance"`
|
||||||
Nonce json.Number `json:"nonce"`
|
// Nonce json.Number `json:"nonce"`
|
||||||
} `json:"result"`
|
// } `json:"result"`
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
type BalanceResult struct {
|
||||||
|
Balance string `json:"balance"`
|
||||||
|
Nonce json.Number `json:"nonce"`
|
||||||
}
|
}
|
||||||
|
@ -4,23 +4,18 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
type Transaction struct {
|
//type Transaction struct {
|
||||||
|
// CreatedAt time.Time `json:"createdAt"`
|
||||||
|
// Status string `json:"status"`
|
||||||
|
// TransferValue json.Number `json:"transferValue"`
|
||||||
|
// TxHash string `json:"txHash"`
|
||||||
|
// TxType string `json:"txType"`
|
||||||
|
//}
|
||||||
|
|
||||||
|
type TrackStatusResult struct {
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
TransferValue json.Number `json:"transferValue"`
|
TransferValue json.Number `json:"transferValue"`
|
||||||
TxHash string `json:"txHash"`
|
TxHash string `json:"txHash"`
|
||||||
TxType string `json:"txType"`
|
TxType string `json:"txType"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TrackStatusResponse struct {
|
|
||||||
Ok bool `json:"ok"`
|
|
||||||
Result struct {
|
|
||||||
Transaction struct {
|
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
TransferValue json.Number `json:"transferValue"`
|
|
||||||
TxHash string `json:"txHash"`
|
|
||||||
TxType string `json:"txType"`
|
|
||||||
}
|
|
||||||
} `json:"result"`
|
|
||||||
}
|
|
||||||
|
@ -1,15 +1 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
// VoucherHoldingResponse represents a single voucher holding
|
|
||||||
type VoucherHoldingResponse struct {
|
|
||||||
Ok bool `json:"ok"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Result struct {
|
|
||||||
Holdings []struct {
|
|
||||||
ContractAddress string `json:"contractAddress"`
|
|
||||||
TokenSymbol string `json:"tokenSymbol"`
|
|
||||||
TokenDecimals string `json:"tokenDecimals"`
|
|
||||||
Balance string `json:"balance"`
|
|
||||||
} `json:"holdings"`
|
|
||||||
} `json:"result"`
|
|
||||||
}
|
|
||||||
|
@ -10,6 +10,14 @@ const (
|
|||||||
DATATYPE_USERSUB = 64
|
DATATYPE_USERSUB = 64
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// PrefixDb interface abstracts the database operations.
|
||||||
|
type PrefixDb interface {
|
||||||
|
Get(ctx context.Context, key []byte) ([]byte, error)
|
||||||
|
Put(ctx context.Context, key []byte, val []byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ PrefixDb = (*SubPrefixDb)(nil)
|
||||||
|
|
||||||
type SubPrefixDb struct {
|
type SubPrefixDb struct {
|
||||||
store db.Db
|
store db.Db
|
||||||
pfx []byte
|
pfx []byte
|
||||||
@ -29,11 +37,7 @@ func (s *SubPrefixDb) toKey(k []byte) []byte {
|
|||||||
func (s *SubPrefixDb) Get(ctx context.Context, key []byte) ([]byte, error) {
|
func (s *SubPrefixDb) Get(ctx context.Context, key []byte) ([]byte, error) {
|
||||||
s.store.SetPrefix(DATATYPE_USERSUB)
|
s.store.SetPrefix(DATATYPE_USERSUB)
|
||||||
key = s.toKey(key)
|
key = s.toKey(key)
|
||||||
v, err := s.store.Get(ctx, key)
|
return s.store.Get(ctx, key)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return v, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubPrefixDb) Put(ctx context.Context, key []byte, val []byte) error {
|
func (s *SubPrefixDb) Put(ctx context.Context, key []byte, val []byte) error {
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"git.grassecon.net/urdt/ussd/internal/models"
|
"git.grassecon.net/urdt/ussd/internal/models"
|
||||||
"github.com/grassrootseconomics/eth-custodial/pkg/api"
|
"github.com/grassrootseconomics/eth-custodial/pkg/api"
|
||||||
|
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -13,28 +14,28 @@ type MockAccountService struct {
|
|||||||
mock.Mock
|
mock.Mock
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockAccountService) CreateAccount(ctx context.Context) (*api.OKResponse, error) {
|
func (m *MockAccountService) CreateAccount(ctx context.Context) (*models.AccountResult, error) {
|
||||||
args := m.Called()
|
args := m.Called()
|
||||||
return args.Get(0).(*api.OKResponse), args.Error(1)
|
return args.Get(0).(*models.AccountResult), args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockAccountService) CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResponse, error) {
|
func (m *MockAccountService) CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResult, error) {
|
||||||
args := m.Called(publicKey)
|
args := m.Called(publicKey)
|
||||||
return args.Get(0).(*models.BalanceResponse), args.Error(1)
|
return args.Get(0).(*models.BalanceResult), args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockAccountService) CheckAccountStatus(ctx context.Context, trackingId string) (*models.TrackStatusResponse, error) {
|
func (m *MockAccountService) TrackAccountStatus(ctx context.Context, trackingId string) (*api.OKResponse, error) {
|
||||||
args := m.Called(trackingId)
|
args := m.Called(trackingId)
|
||||||
return args.Get(0).(*models.TrackStatusResponse), args.Error(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockAccountService) TrackAccountStatus(ctx context.Context,publicKey string) (*api.OKResponse, error) {
|
|
||||||
args := m.Called(publicKey)
|
|
||||||
return args.Get(0).(*api.OKResponse), args.Error(1)
|
return args.Get(0).(*api.OKResponse), args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *MockAccountService) CheckAccountStatus(ctx context.Context,publicKey string) (*models.TrackStatusResult, error) {
|
||||||
func (m *MockAccountService) FetchVouchers(ctx context.Context, publicKey string) (*models.VoucherHoldingResponse, error) {
|
|
||||||
args := m.Called(publicKey)
|
args := m.Called(publicKey)
|
||||||
return args.Get(0).(*models.VoucherHoldingResponse), args.Error(1)
|
return args.Get(0).(*models.TrackStatusResult), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (m *MockAccountService) FetchVouchers(ctx context.Context, publicKey string) ([]dataserviceapi.TokenHoldings, error) {
|
||||||
|
args := m.Called(publicKey)
|
||||||
|
return args.Get(0).([]dataserviceapi.TokenHoldings), args.Error(1)
|
||||||
}
|
}
|
||||||
|
21
internal/testutil/mocks/subprefixdbmock.go
Normal file
21
internal/testutil/mocks/subprefixdbmock.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package mocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MockSubPrefixDb struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockSubPrefixDb) Get(ctx context.Context, key []byte) ([]byte, error) {
|
||||||
|
args := m.Called(ctx, key)
|
||||||
|
return args.Get(0).([]byte), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockSubPrefixDb) Put(ctx context.Context, key, val []byte) error {
|
||||||
|
args := m.Called(ctx, key, val)
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
@ -7,59 +7,35 @@ import (
|
|||||||
|
|
||||||
"git.grassecon.net/urdt/ussd/internal/models"
|
"git.grassecon.net/urdt/ussd/internal/models"
|
||||||
"github.com/grassrootseconomics/eth-custodial/pkg/api"
|
"github.com/grassrootseconomics/eth-custodial/pkg/api"
|
||||||
|
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TestAccountService struct {
|
type TestAccountService struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tas *TestAccountService) CreateAccount(ctx context.Context) (*api.OKResponse, error) {
|
func (tas *TestAccountService) CreateAccount(ctx context.Context) (*models.AccountResult, error) {
|
||||||
return &api.OKResponse{
|
return &models.AccountResult {
|
||||||
Ok: true,
|
TrackingId: "075ccc86-f6ef-4d33-97d5-e91cfb37aa0d",
|
||||||
Description: "Account creation succeeded",
|
PublicKey: "0x623EFAFa8868df4B934dd12a8B26CB3Dd75A7AdD",
|
||||||
Result: map[string]any{
|
|
||||||
"trackingId": "075ccc86-f6ef-4d33-97d5-e91cfb37aa0d",
|
|
||||||
"publicKey": "0x623EFAFa8868df4B934dd12a8B26CB3Dd75A7AdD",
|
|
||||||
},
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tas *TestAccountService) CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResponse, error) {
|
func (tas *TestAccountService) CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResult, error) {
|
||||||
balanceResponse := &models.BalanceResponse{
|
balanceResponse := &models.BalanceResult {
|
||||||
Ok: true,
|
Balance: "0.003 CELO",
|
||||||
Result: struct {
|
Nonce: json.Number("0"),
|
||||||
Balance string `json:"balance"`
|
|
||||||
Nonce json.Number `json:"nonce"`
|
|
||||||
}{
|
|
||||||
Balance: "0.003 CELO",
|
|
||||||
Nonce: json.Number("0"),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return balanceResponse, nil
|
return balanceResponse, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tas *TestAccountService) CheckAccountStatus(ctx context.Context, trackingId string) (*models.TrackStatusResponse, error) {
|
func (tas *TestAccountService) CheckAccountStatus(ctx context.Context, trackingId string) (*models.TrackStatusResult, error) {
|
||||||
trackResponse := &models.TrackStatusResponse{
|
return &models.TrackStatusResult {
|
||||||
Ok: true,
|
CreatedAt: time.Now(),
|
||||||
Result: struct {
|
Status: "SUCCESS",
|
||||||
Transaction struct {
|
TransferValue: json.Number("0.5"),
|
||||||
CreatedAt time.Time "json:\"createdAt\""
|
TxHash: "0x123abc456def",
|
||||||
Status string "json:\"status\""
|
TxType: "transfer",
|
||||||
TransferValue json.Number "json:\"transferValue\""
|
}, nil
|
||||||
TxHash string "json:\"txHash\""
|
|
||||||
TxType string "json:\"txType\""
|
|
||||||
}
|
|
||||||
}{
|
|
||||||
Transaction: models.Transaction{
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
Status: "SUCCESS",
|
|
||||||
TransferValue: json.Number("0.5"),
|
|
||||||
TxHash: "0x123abc456def",
|
|
||||||
TxType: "transfer",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return trackResponse, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tas *TestAccountService) TrackAccountStatus(ctx context.Context, publicKey string) (*api.OKResponse, error) {
|
func (tas *TestAccountService) TrackAccountStatus(ctx context.Context, publicKey string) (*api.OKResponse, error) {
|
||||||
@ -72,30 +48,13 @@ func (tas *TestAccountService) TrackAccountStatus(ctx context.Context, publicKey
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tas *TestAccountService) FetchVouchers(ctx context.Context, publicKey string) (*models.VoucherHoldingResponse, error) {
|
func (tas *TestAccountService) FetchVouchers(ctx context.Context, publicKey string) ([]dataserviceapi.TokenHoldings, error) {
|
||||||
return &models.VoucherHoldingResponse{
|
return []dataserviceapi.TokenHoldings {
|
||||||
Ok: true,
|
dataserviceapi.TokenHoldings {
|
||||||
Result: struct {
|
ContractAddress: "0x6CC75A06ac72eB4Db2eE22F781F5D100d8ec03ee",
|
||||||
Holdings []struct {
|
TokenSymbol: "SRF",
|
||||||
ContractAddress string `json:"contractAddress"`
|
TokenDecimals: "6",
|
||||||
TokenSymbol string `json:"tokenSymbol"`
|
Balance: "2745987",
|
||||||
TokenDecimals string `json:"tokenDecimals"`
|
|
||||||
Balance string `json:"balance"`
|
|
||||||
} `json:"holdings"`
|
|
||||||
}{
|
|
||||||
Holdings: []struct {
|
|
||||||
ContractAddress string `json:"contractAddress"`
|
|
||||||
TokenSymbol string `json:"tokenSymbol"`
|
|
||||||
TokenDecimals string `json:"tokenDecimals"`
|
|
||||||
Balance string `json:"balance"`
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
ContractAddress: "0x6CC75A06ac72eB4Db2eE22F781F5D100d8ec03ee",
|
|
||||||
TokenSymbol: "SRF",
|
|
||||||
TokenDecimals: "6",
|
|
||||||
Balance: "2745987",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
180
internal/utils/vouchers.go
Normal file
180
internal/utils/vouchers.go
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.grassecon.net/urdt/ussd/internal/storage"
|
||||||
|
"git.grassecon.net/urdt/ussd/common"
|
||||||
|
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VoucherMetadata helps organize data fields
|
||||||
|
type VoucherMetadata struct {
|
||||||
|
Symbols string
|
||||||
|
Balances string
|
||||||
|
Decimals string
|
||||||
|
Addresses string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessVouchers converts holdings into formatted strings
|
||||||
|
func ProcessVouchers(holdings []dataserviceapi.TokenHoldings) VoucherMetadata {
|
||||||
|
var data VoucherMetadata
|
||||||
|
var symbols, balances, decimals, addresses []string
|
||||||
|
|
||||||
|
for i, h := range holdings {
|
||||||
|
symbols = append(symbols, fmt.Sprintf("%d:%s", i+1, h.TokenSymbol))
|
||||||
|
balances = append(balances, fmt.Sprintf("%d:%s", i+1, h.Balance))
|
||||||
|
decimals = append(decimals, fmt.Sprintf("%d:%s", i+1, h.TokenDecimals))
|
||||||
|
addresses = append(addresses, fmt.Sprintf("%d:%s", i+1, h.ContractAddress))
|
||||||
|
}
|
||||||
|
|
||||||
|
data.Symbols = strings.Join(symbols, "\n")
|
||||||
|
data.Balances = strings.Join(balances, "\n")
|
||||||
|
data.Decimals = strings.Join(decimals, "\n")
|
||||||
|
data.Addresses = strings.Join(addresses, "\n")
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVoucherData retrieves and matches voucher data
|
||||||
|
func GetVoucherData(ctx context.Context, db storage.PrefixDb, input string) (*dataserviceapi.TokenHoldings, error) {
|
||||||
|
keys := []string{"sym", "bal", "deci", "addr"}
|
||||||
|
data := make(map[string]string)
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
value, err := db.Get(ctx, []byte(key))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get %s: %v", key, err)
|
||||||
|
}
|
||||||
|
data[key] = string(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
symbol, balance, decimal, address := MatchVoucher(input,
|
||||||
|
data["sym"],
|
||||||
|
data["bal"],
|
||||||
|
data["deci"],
|
||||||
|
data["addr"])
|
||||||
|
|
||||||
|
if symbol == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &dataserviceapi.TokenHoldings{
|
||||||
|
TokenSymbol: string(symbol),
|
||||||
|
Balance: string(balance),
|
||||||
|
TokenDecimals: string(decimal),
|
||||||
|
ContractAddress: string(address),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchVoucher finds the matching voucher symbol, balance, decimals and contract address based on the input.
|
||||||
|
func MatchVoucher(input, symbols, balances, decimals, addresses string) (symbol, balance, decimal, address string) {
|
||||||
|
symList := strings.Split(symbols, "\n")
|
||||||
|
balList := strings.Split(balances, "\n")
|
||||||
|
decList := strings.Split(decimals, "\n")
|
||||||
|
addrList := strings.Split(addresses, "\n")
|
||||||
|
|
||||||
|
for i, sym := range symList {
|
||||||
|
parts := strings.SplitN(sym, ":", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if input == parts[0] || strings.EqualFold(input, parts[1]) {
|
||||||
|
symbol = parts[1]
|
||||||
|
if i < len(balList) {
|
||||||
|
balance = strings.SplitN(balList[i], ":", 2)[1]
|
||||||
|
}
|
||||||
|
if i < len(decList) {
|
||||||
|
decimal = strings.SplitN(decList[i], ":", 2)[1]
|
||||||
|
}
|
||||||
|
if i < len(addrList) {
|
||||||
|
address = strings.SplitN(addrList[i], ":", 2)[1]
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoreTemporaryVoucher saves voucher metadata as temporary entries in the common.DataStore.
|
||||||
|
func StoreTemporaryVoucher(ctx context.Context, store common.DataStore, sessionId string, data *dataserviceapi.TokenHoldings) error {
|
||||||
|
entries := map[common.DataTyp][]byte{
|
||||||
|
common.DATA_TEMPORARY_SYM: []byte(data.TokenSymbol),
|
||||||
|
common.DATA_TEMPORARY_BAL: []byte(data.Balance),
|
||||||
|
common.DATA_TEMPORARY_DECIMAL: []byte(data.TokenDecimals),
|
||||||
|
common.DATA_TEMPORARY_ADDRESS: []byte(data.ContractAddress),
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range entries {
|
||||||
|
if err := store.WriteEntry(ctx, sessionId, key, value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTemporaryVoucherData retrieves temporary voucher metadata from the common.DataStore.
|
||||||
|
func GetTemporaryVoucherData(ctx context.Context, store common.DataStore, sessionId string) (*dataserviceapi.TokenHoldings, error) {
|
||||||
|
keys := []common.DataTyp{
|
||||||
|
common.DATA_TEMPORARY_SYM,
|
||||||
|
common.DATA_TEMPORARY_BAL,
|
||||||
|
common.DATA_TEMPORARY_DECIMAL,
|
||||||
|
common.DATA_TEMPORARY_ADDRESS,
|
||||||
|
}
|
||||||
|
|
||||||
|
data := &dataserviceapi.TokenHoldings{}
|
||||||
|
values := make([][]byte, len(keys))
|
||||||
|
|
||||||
|
for i, key := range keys {
|
||||||
|
value, err := store.ReadEntry(ctx, sessionId, key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
values[i] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
data.TokenSymbol = string(values[0])
|
||||||
|
data.Balance = string(values[1])
|
||||||
|
data.TokenDecimals = string(values[2])
|
||||||
|
data.ContractAddress = string(values[3])
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateVoucherData sets the active voucher data and clears the temporary voucher data in the common.DataStore.
|
||||||
|
func UpdateVoucherData(ctx context.Context, store common.DataStore, sessionId string, data *dataserviceapi.TokenHoldings) error {
|
||||||
|
// Active voucher data entries
|
||||||
|
activeEntries := map[common.DataTyp][]byte{
|
||||||
|
common.DATA_ACTIVE_SYM: []byte(data.TokenSymbol),
|
||||||
|
common.DATA_ACTIVE_BAL: []byte(data.Balance),
|
||||||
|
common.DATA_ACTIVE_DECIMAL: []byte(data.TokenDecimals),
|
||||||
|
common.DATA_ACTIVE_ADDRESS: []byte(data.ContractAddress),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear temporary voucher data entries
|
||||||
|
tempEntries := map[common.DataTyp][]byte{
|
||||||
|
common.DATA_TEMPORARY_SYM: []byte(""),
|
||||||
|
common.DATA_TEMPORARY_BAL: []byte(""),
|
||||||
|
common.DATA_TEMPORARY_DECIMAL: []byte(""),
|
||||||
|
common.DATA_TEMPORARY_ADDRESS: []byte(""),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write active data
|
||||||
|
for key, value := range activeEntries {
|
||||||
|
if err := store.WriteEntry(ctx, sessionId, key, value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear temporary data
|
||||||
|
for key, value := range tempEntries {
|
||||||
|
if err := store.WriteEntry(ctx, sessionId, key, value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
223
internal/utils/vouchers_test.go
Normal file
223
internal/utils/vouchers_test.go
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alecthomas/assert/v2"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"git.grassecon.net/urdt/ussd/internal/storage"
|
||||||
|
"git.grassecon.net/urdt/ussd/common"
|
||||||
|
memdb "git.defalsify.org/vise.git/db/mem"
|
||||||
|
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InitializeTestDb sets up and returns an in-memory database and store.
|
||||||
|
func InitializeTestDb(t *testing.T) (context.Context, *common.UserDataStore) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Initialize memDb
|
||||||
|
db := memdb.NewMemDb()
|
||||||
|
err := db.Connect(ctx, "")
|
||||||
|
require.NoError(t, err, "Failed to connect to memDb")
|
||||||
|
|
||||||
|
// Create common.UserDataStore with memDb
|
||||||
|
store := &common.UserDataStore{Db: db}
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
db.Close() // Ensure the DB is closed after each test
|
||||||
|
})
|
||||||
|
|
||||||
|
return ctx, store
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssertEmptyValue checks if a value is empty/nil/zero
|
||||||
|
func AssertEmptyValue(t *testing.T, value []byte, msgAndArgs ...interface{}) {
|
||||||
|
assert.Equal(t, len(value), 0, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMatchVoucher(t *testing.T) {
|
||||||
|
symbols := "1:SRF\n2:MILO"
|
||||||
|
balances := "1:100\n2:200"
|
||||||
|
decimals := "1:6\n2:4"
|
||||||
|
addresses := "1:0xd4c288865Ce\n2:0x41c188d63Qa"
|
||||||
|
|
||||||
|
// Test for valid voucher
|
||||||
|
symbol, balance, decimal, address := MatchVoucher("2", symbols, balances, decimals, addresses)
|
||||||
|
|
||||||
|
// Assertions for valid voucher
|
||||||
|
assert.Equal(t, "MILO", symbol)
|
||||||
|
assert.Equal(t, "200", balance)
|
||||||
|
assert.Equal(t, "4", decimal)
|
||||||
|
assert.Equal(t, "0x41c188d63Qa", address)
|
||||||
|
|
||||||
|
// Test for non-existent voucher
|
||||||
|
symbol, balance, decimal, address = MatchVoucher("3", symbols, balances, decimals, addresses)
|
||||||
|
|
||||||
|
// Assertions for non-match
|
||||||
|
assert.Equal(t, "", symbol)
|
||||||
|
assert.Equal(t, "", balance)
|
||||||
|
assert.Equal(t, "", decimal)
|
||||||
|
assert.Equal(t, "", address)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProcessVouchers(t *testing.T) {
|
||||||
|
holdings := []dataserviceapi.TokenHoldings{
|
||||||
|
{ContractAddress: "0xd4c288865Ce", TokenSymbol: "SRF", TokenDecimals: "6", Balance: "100"},
|
||||||
|
{ContractAddress: "0x41c188d63Qa", TokenSymbol: "MILO", TokenDecimals: "4", Balance: "200"},
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedResult := VoucherMetadata{
|
||||||
|
Symbols: "1:SRF\n2:MILO",
|
||||||
|
Balances: "1:100\n2:200",
|
||||||
|
Decimals: "1:6\n2:4",
|
||||||
|
Addresses: "1:0xd4c288865Ce\n2:0x41c188d63Qa",
|
||||||
|
}
|
||||||
|
|
||||||
|
result := ProcessVouchers(holdings)
|
||||||
|
|
||||||
|
assert.Equal(t, expectedResult, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetVoucherData(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
db := memdb.NewMemDb()
|
||||||
|
err := db.Connect(ctx, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
spdb := storage.NewSubPrefixDb(db, []byte("vouchers"))
|
||||||
|
|
||||||
|
// Test voucher data
|
||||||
|
mockData := map[string][]byte{
|
||||||
|
"sym": []byte("1:SRF\n2:MILO"),
|
||||||
|
"bal": []byte("1:100\n2:200"),
|
||||||
|
"deci": []byte("1:6\n2:4"),
|
||||||
|
"addr": []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put the data
|
||||||
|
for key, value := range mockData {
|
||||||
|
err = spdb.Put(ctx, []byte(key), []byte(value))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := GetVoucherData(ctx, spdb, "1")
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "SRF", result.TokenSymbol)
|
||||||
|
assert.Equal(t, "100", result.Balance)
|
||||||
|
assert.Equal(t, "6", result.TokenDecimals)
|
||||||
|
assert.Equal(t, "0xd4c288865Ce", result.ContractAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStoreTemporaryVoucher(t *testing.T) {
|
||||||
|
ctx, store := InitializeTestDb(t)
|
||||||
|
sessionId := "session123"
|
||||||
|
|
||||||
|
// Test data
|
||||||
|
voucherData := &dataserviceapi.TokenHoldings{
|
||||||
|
TokenSymbol: "SRF",
|
||||||
|
Balance: "200",
|
||||||
|
TokenDecimals: "6",
|
||||||
|
ContractAddress: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the function being tested
|
||||||
|
err := StoreTemporaryVoucher(ctx, store, sessionId, voucherData)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Verify stored data
|
||||||
|
expectedEntries := map[common.DataTyp][]byte{
|
||||||
|
common.DATA_TEMPORARY_SYM: []byte("SRF"),
|
||||||
|
common.DATA_TEMPORARY_BAL: []byte("200"),
|
||||||
|
common.DATA_TEMPORARY_DECIMAL: []byte("6"),
|
||||||
|
common.DATA_TEMPORARY_ADDRESS: []byte("0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9"),
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, expectedValue := range expectedEntries {
|
||||||
|
storedValue, err := store.ReadEntry(ctx, sessionId, key)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expectedValue, storedValue, "Mismatch for key %v", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTemporaryVoucherData(t *testing.T) {
|
||||||
|
ctx, store := InitializeTestDb(t)
|
||||||
|
sessionId := "session123"
|
||||||
|
|
||||||
|
// Test voucher data
|
||||||
|
tempData := &dataserviceapi.TokenHoldings{
|
||||||
|
TokenSymbol: "SRF",
|
||||||
|
Balance: "200",
|
||||||
|
TokenDecimals: "6",
|
||||||
|
ContractAddress: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the data
|
||||||
|
err := StoreTemporaryVoucher(ctx, store, sessionId, tempData)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Execute the function being tested
|
||||||
|
data, err := GetTemporaryVoucherData(ctx, store, sessionId)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tempData, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateVoucherData(t *testing.T) {
|
||||||
|
ctx, store := InitializeTestDb(t)
|
||||||
|
sessionId := "session123"
|
||||||
|
|
||||||
|
// New voucher data
|
||||||
|
newData := &dataserviceapi.TokenHoldings{
|
||||||
|
TokenSymbol: "SRF",
|
||||||
|
Balance: "200",
|
||||||
|
TokenDecimals: "6",
|
||||||
|
ContractAddress: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Old temporary data
|
||||||
|
tempData := &dataserviceapi.TokenHoldings{
|
||||||
|
TokenSymbol: "OLD",
|
||||||
|
Balance: "100",
|
||||||
|
TokenDecimals: "8",
|
||||||
|
ContractAddress: "0xold",
|
||||||
|
}
|
||||||
|
require.NoError(t, StoreTemporaryVoucher(ctx, store, sessionId, tempData))
|
||||||
|
|
||||||
|
// Execute update
|
||||||
|
err := UpdateVoucherData(ctx, store, sessionId, newData)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Verify active data was stored correctly
|
||||||
|
activeEntries := map[common.DataTyp][]byte{
|
||||||
|
common.DATA_ACTIVE_SYM: []byte(newData.TokenSymbol),
|
||||||
|
common.DATA_ACTIVE_BAL: []byte(newData.Balance),
|
||||||
|
common.DATA_ACTIVE_DECIMAL: []byte(newData.TokenDecimals),
|
||||||
|
common.DATA_ACTIVE_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 data mismatch for key %v", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify temporary data was cleared
|
||||||
|
tempKeys := []common.DataTyp{
|
||||||
|
common.DATA_TEMPORARY_SYM,
|
||||||
|
common.DATA_TEMPORARY_BAL,
|
||||||
|
common.DATA_TEMPORARY_DECIMAL,
|
||||||
|
common.DATA_TEMPORARY_ADDRESS,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, key := range tempKeys {
|
||||||
|
storedValue, err := store.ReadEntry(ctx, sessionId, key)
|
||||||
|
require.NoError(t, err)
|
||||||
|
AssertEmptyValue(t, storedValue, "Temporary data not cleared for key %v", key)
|
||||||
|
}
|
||||||
|
}
|
@ -4,27 +4,29 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"net/url"
|
||||||
|
|
||||||
|
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
|
||||||
|
"github.com/grassrootseconomics/eth-custodial/pkg/api"
|
||||||
"git.grassecon.net/urdt/ussd/config"
|
"git.grassecon.net/urdt/ussd/config"
|
||||||
"git.grassecon.net/urdt/ussd/internal/models"
|
"git.grassecon.net/urdt/ussd/internal/models"
|
||||||
"github.com/grassrootseconomics/eth-custodial/pkg/api"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
okResponse api.OKResponse
|
|
||||||
errResponse api.ErrResponse
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type AccountServiceInterface interface {
|
type AccountServiceInterface interface {
|
||||||
CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResponse, error)
|
//CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResponse, error)
|
||||||
CreateAccount(ctx context.Context) (*api.OKResponse, error)
|
CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResult, error)
|
||||||
CheckAccountStatus(ctx context.Context, trackingId string) (*models.TrackStatusResponse, error)
|
CreateAccount(ctx context.Context) (*models.AccountResult, error)
|
||||||
|
//CheckAccountStatus(ctx context.Context, trackingId string) (*models.TrackStatusResponse, error)
|
||||||
|
// TODO: poorly named method seems to be checking transaction
|
||||||
|
CheckAccountStatus(ctx context.Context, trackingId string) (*models.TrackStatusResult, error)
|
||||||
TrackAccountStatus(ctx context.Context, publicKey string) (*api.OKResponse, error)
|
TrackAccountStatus(ctx context.Context, publicKey string) (*api.OKResponse, error)
|
||||||
FetchVouchers(ctx context.Context, publicKey string) (*models.VoucherHoldingResponse, error)
|
FetchVouchers(ctx context.Context, publicKey string) ([]dataserviceapi.TokenHoldings, error)
|
||||||
|
//FetchVouchers(ctx context.Context, publicKey string) (*api.OKResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type AccountService struct {
|
type AccountService struct {
|
||||||
@ -39,32 +41,38 @@ type AccountService struct {
|
|||||||
// - string: The status of the transaction as a string. If there is an error during the request or processing, this will be an empty string.
|
// - string: The status of the transaction as a string. If there is an error during the request or processing, this will be an empty string.
|
||||||
// - error: An error if any occurred during the HTTP request, reading the response, or unmarshalling the JSON data.
|
// - error: An error if any occurred during the HTTP request, reading the response, or unmarshalling the JSON data.
|
||||||
// If no error occurs, this will be nil
|
// If no error occurs, this will be nil
|
||||||
func (as *AccountService) CheckAccountStatus(ctx context.Context, trackingId string) (*models.TrackStatusResponse, error) {
|
func (as *AccountService) CheckAccountStatus(ctx context.Context, trackingId string) (*models.TrackStatusResult, error) {
|
||||||
resp, err := http.Get(config.BalanceURL + trackingId)
|
var r models.TrackStatusResult
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
ep, err := url.JoinPath(config.TrackURL, trackingId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var trackResp models.TrackStatusResponse
|
req, err := http.NewRequest("GET", ep, nil)
|
||||||
err = json.Unmarshal(body, &trackResp)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &trackResp, nil
|
|
||||||
|
|
||||||
|
_, err = doCustodialRequest(ctx, req, &r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (as *AccountService) TrackAccountStatus(ctx context.Context, publicKey string) (*api.OKResponse, error) {
|
func (as *AccountService) TrackAccountStatus(ctx context.Context, publicKey string) (*api.OKResponse, error) {
|
||||||
|
var okResponse api.OKResponse
|
||||||
|
var errResponse api.ErrResponse
|
||||||
var err error
|
var err error
|
||||||
// Construct the URL with the path parameter
|
|
||||||
url := fmt.Sprintf("%s/%s", config.TrackURL, publicKey)
|
ep, err := url.JoinPath(config.TrackURL, publicKey)
|
||||||
req, err := http.NewRequest("GET", url, nil)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", ep, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -104,41 +112,83 @@ func (as *AccountService) TrackAccountStatus(ctx context.Context, publicKey stri
|
|||||||
// CheckBalance retrieves the balance for a given public key from the custodial balance API endpoint.
|
// CheckBalance retrieves the balance for a given public key from the custodial balance API endpoint.
|
||||||
// Parameters:
|
// Parameters:
|
||||||
// - publicKey: The public key associated with the account whose balance needs to be checked.
|
// - publicKey: The public key associated with the account whose balance needs to be checked.
|
||||||
func (as *AccountService) CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResponse, error) {
|
func (as *AccountService) CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResult, error) {
|
||||||
resp, err := http.Get(config.BalanceURL + publicKey)
|
var balanceResult models.BalanceResult
|
||||||
|
|
||||||
|
ep, err := url.JoinPath(config.BalanceURL, publicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
req, err := http.NewRequest("GET", ep, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var balanceResp models.BalanceResponse
|
|
||||||
err = json.Unmarshal(body, &balanceResp)
|
_, err = doCustodialRequest(ctx, req, &balanceResult)
|
||||||
if err != nil {
|
return &balanceResult, err
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &balanceResp, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// CreateAccount creates a new account in the custodial system.
|
// CreateAccount creates a new account in the custodial system.
|
||||||
// Returns:
|
// Returns:
|
||||||
// - *models.AccountResponse: A pointer to an AccountResponse struct containing the details of the created account.
|
// - *models.AccountResponse: A pointer to an AccountResponse struct containing the details of the created account.
|
||||||
// If there is an error during the request or processing, this will be nil.
|
// If there is an error during the request or processing, this will be nil.
|
||||||
// - error: An error if any occurred during the HTTP request, reading the response, or unmarshalling the JSON data.
|
// - error: An error if any occurred during the HTTP request, reading the response, or unmarshalling the JSON data.
|
||||||
// If no error occurs, this will be nil.
|
// If no error occurs, this will be nil.
|
||||||
func (as *AccountService) CreateAccount(ctx context.Context) (*api.OKResponse, error) {
|
func (as *AccountService) CreateAccount(ctx context.Context) (*models.AccountResult, error) {
|
||||||
var err error
|
var r models.AccountResult
|
||||||
|
|
||||||
// Create a new request
|
// Create a new request
|
||||||
req, err := http.NewRequest("POST", config.CreateAccountURL, nil)
|
req, err := http.NewRequest("POST", config.CreateAccountURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
req.Header.Set("X-GE-KEY", "xd")
|
|
||||||
|
|
||||||
|
_, err = doCustodialRequest(ctx, req, &r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchVouchers retrieves the token holdings for a given public key from the custodial holdings API endpoint
|
||||||
|
// Parameters:
|
||||||
|
// - publicKey: The public key associated with the account.
|
||||||
|
func (as *AccountService) FetchVouchers(ctx context.Context, publicKey string) ([]dataserviceapi.TokenHoldings, error) {
|
||||||
|
var r []dataserviceapi.TokenHoldings
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", config.VoucherHoldingsURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = doDataRequest(ctx, req, r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, nil
|
||||||
|
|
||||||
|
//
|
||||||
|
// file, err := os.Open("sample_tokens.json")
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
// defer file.Close()
|
||||||
|
// var holdings models.VoucherHoldingResponse
|
||||||
|
//
|
||||||
|
// if err := json.NewDecoder(file).Decode(&holdings); err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
// return &holdings, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func doRequest(ctx context.Context, req *http.Request, rcpt any) (*api.OKResponse, error) {
|
||||||
|
var okResponse api.OKResponse
|
||||||
|
var errResponse api.ErrResponse
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errResponse.Description = err.Error()
|
errResponse.Description = err.Error()
|
||||||
@ -165,21 +215,22 @@ func (as *AccountService) CreateAccount(ctx context.Context) (*api.OKResponse, e
|
|||||||
return nil, errors.New("Empty api result")
|
return nil, errors.New("Empty api result")
|
||||||
}
|
}
|
||||||
return &okResponse, nil
|
return &okResponse, nil
|
||||||
}
|
|
||||||
|
|
||||||
// FetchVouchers retrieves the token holdings for a given public key from the custodial holdings API endpoint
|
v, err := json.Marshal(okResponse.Result)
|
||||||
// Parameters:
|
|
||||||
// - publicKey: The public key associated with the account.
|
|
||||||
func (as *AccountService) FetchVouchers(ctx context.Context, publicKey string) (*models.VoucherHoldingResponse, error) {
|
|
||||||
file, err := os.Open("sample_tokens.json")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
|
||||||
var holdings models.VoucherHoldingResponse
|
|
||||||
|
|
||||||
if err := json.NewDecoder(file).Decode(&holdings); err != nil {
|
err = json.Unmarshal(v, &rcpt)
|
||||||
return nil, err
|
return &okResponse, err
|
||||||
}
|
}
|
||||||
return &holdings, nil
|
|
||||||
|
func doCustodialRequest(ctx context.Context, req *http.Request, rcpt any) (*api.OKResponse, error) {
|
||||||
|
req.Header.Set("X-GE-KEY", config.CustodialAPIKey)
|
||||||
|
return doRequest(ctx, req, rcpt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doDataRequest(ctx context.Context, req *http.Request, rcpt any) (*api.OKResponse, error) {
|
||||||
|
req.Header.Set("X-GE-KEY", config.DataAPIKey)
|
||||||
|
return doRequest(ctx, req, rcpt)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user