From 93df6a6a08d5f6c21ee67c1848a4336a0f318465 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 14 Nov 2024 14:59:39 +0300 Subject: [PATCH 01/24] validate recipients and invite valid ones --- internal/handlers/handlerservice.go | 1 + internal/handlers/ussd/menuhandler.go | 58 +++++++++++++++++++-- services/registration/invalid_recipient | 2 +- services/registration/invalid_recipient_swa | 2 +- services/registration/invite_menu | 1 + services/registration/invite_menu_swa | 1 + services/registration/invite_recipient | 1 + services/registration/invite_recipient.vis | 8 +++ services/registration/invite_recipient_swa | 1 + services/registration/invite_result.vis | 2 + services/registration/locale/swa/default.po | 6 +++ services/registration/send.vis | 2 + 12 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 services/registration/invite_menu create mode 100644 services/registration/invite_menu_swa create mode 100644 services/registration/invite_recipient create mode 100644 services/registration/invite_recipient.vis create mode 100644 services/registration/invite_recipient_swa create mode 100644 services/registration/invite_result.vis diff --git a/internal/handlers/handlerservice.go b/internal/handlers/handlerservice.go index 7a1e912..358d492 100644 --- a/internal/handlers/handlerservice.go +++ b/internal/handlers/handlerservice.go @@ -80,6 +80,7 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountServiceIn ls.DbRs.AddLocalFunc("check_balance", ussdHandlers.CheckBalance) ls.DbRs.AddLocalFunc("validate_recipient", ussdHandlers.ValidateRecipient) ls.DbRs.AddLocalFunc("transaction_reset", ussdHandlers.TransactionReset) + ls.DbRs.AddLocalFunc("invite_valid_recipient", ussdHandlers.InviteValidRecipient) ls.DbRs.AddLocalFunc("max_amount", ussdHandlers.MaxAmount) ls.DbRs.AddLocalFunc("validate_amount", ussdHandlers.ValidateAmount) ls.DbRs.AddLocalFunc("reset_transaction_amount", ussdHandlers.ResetTransactionAmount) diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index 31d0e30..138ebca 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -929,7 +929,7 @@ func (h *Handlers) ValidateBlockedNumber(ctx context.Context, sym string, input // ValidateRecipient validates that the given input is a valid phone number. func (h *Handlers) ValidateRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result - var err error + store := h.userdataStore sessionId, ok := ctx.Value("SessionId").(string) if !ok { @@ -939,18 +939,41 @@ func (h *Handlers) ValidateRecipient(ctx context.Context, sym string, input []by recipient := string(input) flag_invalid_recipient, _ := h.flagManager.GetFlag("flag_invalid_recipient") + flag_invalid_recipient_with_invite, _ := h.flagManager.GetFlag("flag_invalid_recipient_with_invite") if recipient != "0" { - // mimic invalid number check - if recipient == "000" { + if !isValidPhoneNumber(recipient) { res.FlagSet = append(res.FlagSet, flag_invalid_recipient) res.Content = recipient return res, nil } - store := h.userdataStore - err = store.WriteEntry(ctx, sessionId, common.DATA_RECIPIENT, []byte(recipient)) + + publicKey, err := store.ReadEntry(ctx, recipient, common.DATA_PUBLIC_KEY) if err != nil { + if db.IsNotFound(err) { + logg.InfoCtxf(ctx, "Unregistered number") + + // save the recipient as the temporaryInvitedNumber + err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(recipient)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write temporaryInvitedNumber entry with", "key", common.DATA_TEMPORARY_VALUE, "value", recipient, "error", err) + return res, err + } + + res.FlagSet = append(res.FlagSet, flag_invalid_recipient_with_invite) + res.Content = recipient + + return res, nil + } + + logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", common.DATA_PUBLIC_KEY, "error", err) + return res, err + } + + err = store.WriteEntry(ctx, sessionId, common.DATA_RECIPIENT, publicKey) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write recipient entry with", "key", common.DATA_RECIPIENT, "value", string(publicKey), "error", err) return res, nil } } @@ -987,6 +1010,31 @@ func (h *Handlers) TransactionReset(ctx context.Context, sym string, input []byt return res, nil } +// InviteValidRecipient sends an invitation to the valid phone number. +func (h *Handlers) InviteValidRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + store := h.userdataStore + + sessionId, ok := ctx.Value("SessionId").(string) + if !ok { + return res, fmt.Errorf("missing session") + } + + code := codeFromCtx(ctx) + l := gotext.NewLocale(translationDir, code) + l.AddDomain("default") + + recipient, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE) + + // TODO + // send an invitation SMS + // if successful + // res.Content = l.Get("Your invitation to %s to join Sarafu Network has been sent.", string(recipient)) + + res.Content = l.Get("Your invite request for %s to Sarafu Network failed. Please try again later.", string(recipient)) + return res, nil +} + // ResetTransactionAmount resets the transaction amount and invalid flag func (h *Handlers) ResetTransactionAmount(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result diff --git a/services/registration/invalid_recipient b/services/registration/invalid_recipient index 0be78bd..d9fcb1d 100644 --- a/services/registration/invalid_recipient +++ b/services/registration/invalid_recipient @@ -1 +1 @@ -{{.validate_recipient}} is not registered or invalid, please try again: \ No newline at end of file +{{.validate_recipient}} is invalid, please try again: \ No newline at end of file diff --git a/services/registration/invalid_recipient_swa b/services/registration/invalid_recipient_swa index 39e7804..13dda97 100644 --- a/services/registration/invalid_recipient_swa +++ b/services/registration/invalid_recipient_swa @@ -1 +1 @@ -{{.validate_recipient}} haijasajiliwa au sio sahihi, tafadhali weka tena: \ No newline at end of file +{{.validate_recipient}} sio sahihi, tafadhali weka tena: \ No newline at end of file diff --git a/services/registration/invite_menu b/services/registration/invite_menu new file mode 100644 index 0000000..e44862a --- /dev/null +++ b/services/registration/invite_menu @@ -0,0 +1 @@ +Invite to Sarafu Network \ No newline at end of file diff --git a/services/registration/invite_menu_swa b/services/registration/invite_menu_swa new file mode 100644 index 0000000..48c0ddf --- /dev/null +++ b/services/registration/invite_menu_swa @@ -0,0 +1 @@ +Karibisha kwa matandao wa Sarafu \ No newline at end of file diff --git a/services/registration/invite_recipient b/services/registration/invite_recipient new file mode 100644 index 0000000..aa3438d --- /dev/null +++ b/services/registration/invite_recipient @@ -0,0 +1 @@ +{{.validate_recipient}} is not registered, please try again: \ No newline at end of file diff --git a/services/registration/invite_recipient.vis b/services/registration/invite_recipient.vis new file mode 100644 index 0000000..1a4845f --- /dev/null +++ b/services/registration/invite_recipient.vis @@ -0,0 +1,8 @@ +MAP validate_recipient +MOUT retry 1 +MOUT invite 2 +MOUT quit 9 +HALT +INCMP _ 1 +INCMP invite_result 2 +INCMP quit 9 diff --git a/services/registration/invite_recipient_swa b/services/registration/invite_recipient_swa new file mode 100644 index 0000000..30cf599 --- /dev/null +++ b/services/registration/invite_recipient_swa @@ -0,0 +1 @@ +{{.validate_recipient}} haijasajiliwa, tafadhali weka tena: \ No newline at end of file diff --git a/services/registration/invite_result.vis b/services/registration/invite_result.vis new file mode 100644 index 0000000..5f31749 --- /dev/null +++ b/services/registration/invite_result.vis @@ -0,0 +1,2 @@ +LOAD invite_valid_recipient 0 +HALT diff --git a/services/registration/locale/swa/default.po b/services/registration/locale/swa/default.po index ba9a9bb..3e5213c 100644 --- a/services/registration/locale/swa/default.po +++ b/services/registration/locale/swa/default.po @@ -12,3 +12,9 @@ msgstr "Kwa usaidizi zaidi,piga: 0757628885" msgid "Balance: %s\n" msgstr "Salio: %s\n" + +msid "Your invite request for %s to Sarafu Network failed. Please try again later." +msgstr "Ombi lako la kumwalika %s kwa matandao wa Sarafu halikufaulu. Tafadhali jaribu tena baadaye." + +msgid "Your invitation to %s to join Sarafu Network has been sent." +msgstr "Ombi lako la kumwalika %s kwa matandao wa Sarafu limetumwa." diff --git a/services/registration/send.vis b/services/registration/send.vis index 0ff0927..8928725 100644 --- a/services/registration/send.vis +++ b/services/registration/send.vis @@ -1,9 +1,11 @@ LOAD transaction_reset 0 +RELOAD transaction_reset CATCH no_voucher flag_no_active_voucher 1 MOUT back 0 HALT LOAD validate_recipient 20 RELOAD validate_recipient CATCH invalid_recipient flag_invalid_recipient 1 +CATCH invite_recipient flag_invalid_recipient_with_invite 1 INCMP _ 0 INCMP amount * From 9d2d01e3e21e311195ec77bc370bf7bb8b29c21d Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 14 Nov 2024 17:30:13 +0300 Subject: [PATCH 02/24] save the recipient number in DATA_TEMPORARY_VALUE --- internal/handlers/ussd/menuhandler.go | 24 +++++++++++------------- services/registration/amount.vis | 4 ++-- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index 138ebca..055a314 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -929,6 +929,7 @@ func (h *Handlers) ValidateBlockedNumber(ctx context.Context, sym string, input // ValidateRecipient validates that the given input is a valid phone number. func (h *Handlers) ValidateRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result + var err error store := h.userdataStore sessionId, ok := ctx.Value("SessionId").(string) @@ -949,18 +950,18 @@ func (h *Handlers) ValidateRecipient(ctx context.Context, sym string, input []by return res, nil } + // save the recipient as the temporaryRecipient + err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(recipient)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write temporaryRecipient entry with", "key", common.DATA_TEMPORARY_VALUE, "value", recipient, "error", err) + return res, err + } + publicKey, err := store.ReadEntry(ctx, recipient, common.DATA_PUBLIC_KEY) if err != nil { if db.IsNotFound(err) { logg.InfoCtxf(ctx, "Unregistered number") - // save the recipient as the temporaryInvitedNumber - err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(recipient)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write temporaryInvitedNumber entry with", "key", common.DATA_TEMPORARY_VALUE, "value", recipient, "error", err) - return res, err - } - res.FlagSet = append(res.FlagSet, flag_invalid_recipient_with_invite) res.Content = recipient @@ -1133,7 +1134,7 @@ func (h *Handlers) ValidateAmount(ctx context.Context, sym string, input []byte) return res, nil } -// GetRecipient returns the transaction recipient from the gdbm. +// GetRecipient returns the transaction recipient phone number from the gdbm. func (h *Handlers) GetRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result @@ -1142,7 +1143,7 @@ func (h *Handlers) GetRecipient(ctx context.Context, sym string, input []byte) ( return res, fmt.Errorf("missing session") } store := h.userdataStore - recipient, _ := store.ReadEntry(ctx, sessionId, common.DATA_RECIPIENT) + recipient, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE) res.Content = string(recipient) @@ -1219,11 +1220,8 @@ func (h *Handlers) InitiateTransaction(ctx context.Context, sym string, input [] // TODO // Use the amount, recipient and sender to call the API and initialize the transaction store := h.userdataStore - amount, _ := store.ReadEntry(ctx, sessionId, common.DATA_AMOUNT) - - recipient, _ := store.ReadEntry(ctx, sessionId, common.DATA_RECIPIENT) - + recipient, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE) activeSym, _ := store.ReadEntry(ctx, sessionId, common.DATA_ACTIVE_SYM) res.Content = l.Get("Your request has been sent. %s will receive %s %s from %s.", string(recipient), string(amount), string(activeSym), string(sessionId)) diff --git a/services/registration/amount.vis b/services/registration/amount.vis index 82e1fd4..2266160 100644 --- a/services/registration/amount.vis +++ b/services/registration/amount.vis @@ -6,10 +6,10 @@ MOUT back 0 HALT LOAD validate_amount 64 RELOAD validate_amount -CATCH api_failure flag_api_call_error 1 +CATCH api_failure flag_api_call_error 1 CATCH invalid_amount flag_invalid_amount 1 INCMP _ 0 -LOAD get_recipient 12 +LOAD get_recipient 0 LOAD get_sender 64 LOAD get_amount 32 INCMP transaction_pin * From f3e3badff68044c04ac96badd8b33b49582bd1d4 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 14 Nov 2024 17:31:17 +0300 Subject: [PATCH 03/24] save the entire voucher data when setting the default voucher --- internal/handlers/ussd/menuhandler.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index 055a314..68580c2 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -1447,6 +1447,8 @@ func (h *Handlers) SetDefaultVoucher(ctx context.Context, sym string, input []by firstVoucher := vouchersResp[0] defaultSym := firstVoucher.TokenSymbol defaultBal := firstVoucher.Balance + defaultDec := firstVoucher.TokenDecimals + defaultAddr := firstVoucher.ContractAddress // set the active symbol err = store.WriteEntry(ctx, sessionId, common.DATA_ACTIVE_SYM, []byte(defaultSym)) @@ -1460,6 +1462,18 @@ func (h *Handlers) SetDefaultVoucher(ctx context.Context, sym string, input []by logg.ErrorCtxf(ctx, "failed to write defaultBal entry with", "key", common.DATA_ACTIVE_BAL, "value", defaultBal, "error", err) return res, err } + // set the active decimals + err = store.WriteEntry(ctx, sessionId, common.DATA_ACTIVE_DECIMAL, []byte(defaultDec)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write defaultDec entry with", "key", common.DATA_ACTIVE_DECIMAL, "value", defaultDec, "error", err) + return res, err + } + // set the active contract address + err = store.WriteEntry(ctx, sessionId, common.DATA_ACTIVE_ADDRESS, []byte(defaultAddr)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write defaultAddr entry with", "key", common.DATA_ACTIVE_ADDRESS, "value", defaultAddr, "error", err) + return res, err + } return res, nil } From fabcccfa6048d5eeec89a094d5bec2b7c6807778 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 14 Nov 2024 19:35:44 +0300 Subject: [PATCH 04/24] added the tokenTransfer url --- config/config.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/config.go b/config/config.go index c4445eb..0d39e6e 100644 --- a/config/config.go +++ b/config/config.go @@ -11,6 +11,7 @@ const ( trackStatusPath = "/api/track" balancePathPrefix = "/api/account" trackPath = "/api/v2/account/status" + tokenTransferPrefix = "/api/v2/token/transfer" voucherHoldingsPathPrefix = "/api/v1/holdings" voucherTransfersPathPrefix = "/api/v1/transfers/last10" voucherDataPathPrefix = "/api/v1/token" @@ -28,6 +29,7 @@ var ( TrackStatusURL string BalanceURL string TrackURL string + TokenTransferURL string VoucherHoldingsURL string VoucherTransfersURL string VoucherDataURL string @@ -62,6 +64,7 @@ func LoadConfig() error { TrackStatusURL, _ = url.JoinPath(custodialURLBase, trackStatusPath) BalanceURL, _ = url.JoinPath(custodialURLBase, balancePathPrefix) TrackURL, _ = url.JoinPath(custodialURLBase, trackPath) + TokenTransferURL, _ = url.JoinPath(custodialURLBase, tokenTransferPrefix) VoucherHoldingsURL, _ = url.JoinPath(dataURLBase, voucherHoldingsPathPrefix) VoucherTransfersURL, _ = url.JoinPath(dataURLBase, voucherTransfersPathPrefix) VoucherDataURL, _ = url.JoinPath(dataURLBase, voucherDataPathPrefix) From c10e1a6a1ba6dde3d2bbcf674403faaad6d746ea Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 14 Nov 2024 19:36:30 +0300 Subject: [PATCH 05/24] added the TokenTransferResponse model --- models/token_transfer_response.go | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 models/token_transfer_response.go diff --git a/models/token_transfer_response.go b/models/token_transfer_response.go new file mode 100644 index 0000000..b4d6dc3 --- /dev/null +++ b/models/token_transfer_response.go @@ -0,0 +1,5 @@ +package models + +type TokenTransferResponse struct { + TrackingId string `json:"trackingId"` +} From c34906cb1f9fe4ec602a10819b4e7cef6110c539 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 14 Nov 2024 20:02:48 +0300 Subject: [PATCH 06/24] added the TokenTransfer functionality and mocks --- internal/testutil/mocks/servicemock.go | 8 +++-- .../testservice/TestAccountService.go | 18 ++++++---- remote/accountservice.go | 36 +++++++++++++++++++ 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/internal/testutil/mocks/servicemock.go b/internal/testutil/mocks/servicemock.go index 76803ba..a25f1ae 100644 --- a/internal/testutil/mocks/servicemock.go +++ b/internal/testutil/mocks/servicemock.go @@ -28,7 +28,6 @@ func (m *MockAccountService) TrackAccountStatus(ctx context.Context, trackingId 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) @@ -39,7 +38,12 @@ func (m *MockAccountService) FetchTransactions(ctx context.Context, publicKey st return args.Get(0).([]dataserviceapi.Last10TxResponse), args.Error(1) } -func(m MockAccountService) VoucherData(ctx context.Context, address string) (*models.VoucherDataResult, error) { +func (m *MockAccountService) VoucherData(ctx context.Context, address string) (*models.VoucherDataResult, error) { args := m.Called(address) return args.Get(0).(*models.VoucherDataResult), args.Error(1) } + +func (m *MockAccountService) TokenTransfer(ctx context.Context, amount, from, to, tokenAddress string) (*models.TokenTransferResponse, error) { + args := m.Called() + return args.Get(0).(*models.TokenTransferResponse), args.Error(1) +} diff --git a/internal/testutil/testservice/TestAccountService.go b/internal/testutil/testservice/TestAccountService.go index 8752d6f..7c486f3 100644 --- a/internal/testutil/testservice/TestAccountService.go +++ b/internal/testutil/testservice/TestAccountService.go @@ -12,14 +12,14 @@ type TestAccountService struct { } func (tas *TestAccountService) CreateAccount(ctx context.Context) (*models.AccountResult, error) { - return &models.AccountResult { + return &models.AccountResult{ TrackingId: "075ccc86-f6ef-4d33-97d5-e91cfb37aa0d", - PublicKey: "0x623EFAFa8868df4B934dd12a8B26CB3Dd75A7AdD", + PublicKey: "0x623EFAFa8868df4B934dd12a8B26CB3Dd75A7AdD", }, nil } func (tas *TestAccountService) CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResult, error) { - balanceResponse := &models.BalanceResult { + balanceResponse := &models.BalanceResult{ Balance: "0.003 CELO", Nonce: json.Number("0"), } @@ -27,7 +27,7 @@ func (tas *TestAccountService) CheckBalance(ctx context.Context, publicKey strin } func (tas *TestAccountService) TrackAccountStatus(ctx context.Context, publicKey string) (*models.TrackStatusResult, error) { - return &models.TrackStatusResult { + return &models.TrackStatusResult{ Active: true, }, nil } @@ -40,13 +40,19 @@ func (tas *TestAccountService) FetchVouchers(ctx context.Context, publicKey stri TokenDecimals: "6", Balance: "2745987", }, - }, nil + }, nil } func (tas *TestAccountService) FetchTransactions(ctx context.Context, publicKey string) ([]dataserviceapi.Last10TxResponse, error) { return []dataserviceapi.Last10TxResponse{}, nil } -func(m TestAccountService) VoucherData(ctx context.Context, address string) (*models.VoucherDataResult, error) { +func (m TestAccountService) VoucherData(ctx context.Context, address string) (*models.VoucherDataResult, error) { return &models.VoucherDataResult{}, nil } + +func (tas *TestAccountService) TokenTransfer(ctx context.Context, amount, from, to, tokenAddress string) (*models.TokenTransferResponse, error) { + return &models.TokenTransferResponse{ + TrackingId: "e034d147-747d-42ea-928d-b5a7cb3426af", + }, nil +} diff --git a/remote/accountservice.go b/remote/accountservice.go index 19914cd..3af1ab1 100644 --- a/remote/accountservice.go +++ b/remote/accountservice.go @@ -23,6 +23,7 @@ type AccountServiceInterface interface { FetchVouchers(ctx context.Context, publicKey string) ([]dataserviceapi.TokenHoldings, error) FetchTransactions(ctx context.Context, publicKey string) ([]dataserviceapi.Last10TxResponse, error) VoucherData(ctx context.Context, address string) (*models.VoucherDataResult, error) + TokenTransfer(ctx context.Context, amount, from, to, tokenAddress string) (*models.TokenTransferResponse, error) } type AccountService struct { @@ -167,6 +168,41 @@ func (as *AccountService) VoucherData(ctx context.Context, address string) (*mod return &voucherDataResult, err } +// TokenTransfer creates a new token transfer in the custodial system. +// Returns: +// - *models.TokenTransferResponse: A pointer to an TokenTransferResponse struct containing the trackingId. +// 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. +// If no error occurs, this will be nil. +func (as *AccountService) TokenTransfer(ctx context.Context, amount, from, to, tokenAddress string) (*models.TokenTransferResponse, error) { + var r models.TokenTransferResponse + + // Create request payload + payload := map[string]string{ + "amount": amount, + "from": from, + "to": to, + "tokenAddress": tokenAddress, + } + + payloadBytes, err := json.Marshal(payload) + if err != nil { + return nil, err + } + + // Create a new request + req, err := http.NewRequest("POST", config.TokenTransferURL, bytes.NewBuffer(payloadBytes)) + if err != nil { + return nil, err + } + _, err = doCustodialRequest(ctx, req, &r) + if err != nil { + return nil, err + } + + return &r, nil +} + func doRequest(ctx context.Context, req *http.Request, rcpt any) (*api.OKResponse, error) { var okResponse api.OKResponse var errResponse api.ErrResponse From 7985b20200f34f75c494aaef4b7ca7730ffe32f0 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 14 Nov 2024 20:09:50 +0300 Subject: [PATCH 07/24] added message string for failed transaction --- services/registration/locale/swa/default.po | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/registration/locale/swa/default.po b/services/registration/locale/swa/default.po index 3e5213c..f933dde 100644 --- a/services/registration/locale/swa/default.po +++ b/services/registration/locale/swa/default.po @@ -18,3 +18,6 @@ msgstr "Ombi lako la kumwalika %s kwa matandao wa Sarafu halikufaulu. Tafadhali msgid "Your invitation to %s to join Sarafu Network has been sent." msgstr "Ombi lako la kumwalika %s kwa matandao wa Sarafu limetumwa." + +msgid "Your request failed. Please try again later." +msgstr "Ombi lako halikufaulu. Tafadhali jaribu tena baadaye." From 5773305785e9a761a8751965a961bae410961620 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 14 Nov 2024 20:10:48 +0300 Subject: [PATCH 08/24] parse data and initialize a transaction --- internal/handlers/ussd/menuhandler.go | 49 ++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index 68580c2..00c6ad7 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "math/big" "path" "regexp" "strconv" @@ -702,7 +703,7 @@ func (h *Handlers) CheckAccountStatus(ctx context.Context, sym string, input []b r, err := h.accountService.TrackAccountStatus(ctx, string(publicKey)) if err != nil { res.FlagSet = append(res.FlagSet, flag_api_error) - logg.ErrorCtxf(ctx, "failed on TrackAccountStatus", err) + logg.ErrorCtxf(ctx, "failed on TrackAccountStatus", "error", err) return res, err } @@ -1214,24 +1215,56 @@ func (h *Handlers) InitiateTransaction(ctx context.Context, sym string, input [] return res, fmt.Errorf("missing session") } + flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") + account_authorized_flag, _ := h.flagManager.GetFlag("flag_account_authorized") + code := codeFromCtx(ctx) l := gotext.NewLocale(translationDir, code) l.AddDomain("default") - // TODO - // Use the amount, recipient and sender to call the API and initialize the transaction + store := h.userdataStore - amount, _ := store.ReadEntry(ctx, sessionId, common.DATA_AMOUNT) - recipient, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE) + recipientNumber, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE) activeSym, _ := store.ReadEntry(ctx, sessionId, common.DATA_ACTIVE_SYM) - res.Content = l.Get("Your request has been sent. %s will receive %s %s from %s.", string(recipient), string(amount), string(activeSym), string(sessionId)) + storedAmount, _ := store.ReadEntry(ctx, sessionId, common.DATA_AMOUNT) + fromAddress, _ := store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY) + toAddress, _ := store.ReadEntry(ctx, sessionId, common.DATA_RECIPIENT) + activeDecimal, _ := store.ReadEntry(ctx, sessionId, common.DATA_ACTIVE_DECIMAL) + tokenAddress, _ := store.ReadEntry(ctx, sessionId, common.DATA_ACTIVE_ADDRESS) - account_authorized_flag, err := h.flagManager.GetFlag("flag_account_authorized") + // Parse tokendecimal + tokenDecimal, err := strconv.Atoi(string(activeDecimal)) if err != nil { - logg.ErrorCtxf(ctx, "Failed to set the flag_account_authorized", "error", err) return res, err } + // Parse amount and scale it + amount, _, err := big.ParseFloat(string(storedAmount), 10, 0, big.ToZero) + if err != nil { + return res, err + } + + multiplier := new(big.Float).SetInt(new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(tokenDecimal)), nil)) + finalAmount := new(big.Float).Mul(amount, multiplier) + + // Convert finalAmount to a string + finalAmountStr := new(big.Int) + finalAmount.Int(finalAmountStr) + + // Call TokenTransfer + r, err := h.accountService.TokenTransfer(ctx, finalAmountStr.String(), string(fromAddress), string(toAddress), string(tokenAddress)) + if err != nil { + res.Content = l.Get("Your request failed. Please try again later.") + res.FlagSet = append(res.FlagSet, flag_api_error) + logg.ErrorCtxf(ctx, "failed on TokenTransfer", "error", err) + return res, err + } + + trackingId := r.TrackingId + logg.InfoCtxf(ctx, "TokenTransfer", "trackingId", trackingId) + + res.Content = l.Get("Your request has been sent. %s will receive %s %s from %s.", string(recipientNumber), string(storedAmount), string(activeSym), string(sessionId)) + res.FlagReset = append(res.FlagReset, account_authorized_flag) return res, nil } From de5ecc5fe76fb9d0c07be99d21f1bb315d5a3cdb Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 14 Nov 2024 21:21:04 +0300 Subject: [PATCH 09/24] add the tokens functionality to the common package --- common/tokens.go | 54 +++++++++++++++++++++++++++ internal/handlers/ussd/menuhandler.go | 49 +++++++++--------------- 2 files changed, 71 insertions(+), 32 deletions(-) create mode 100644 common/tokens.go diff --git a/common/tokens.go b/common/tokens.go new file mode 100644 index 0000000..727cbea --- /dev/null +++ b/common/tokens.go @@ -0,0 +1,54 @@ +package common + +import ( + "context" + "math/big" + "strconv" +) + +func ParseAndScaleAmount(storedAmount, activeDecimal []byte) (string, error) { + // Parse token decimal + tokenDecimal, err := strconv.Atoi(string(activeDecimal)) + if err != nil { + + return "", err + } + + // Parse amount + amount, _, err := big.ParseFloat(string(storedAmount), 10, 0, big.ToZero) + if err != nil { + return "", err + } + + // Scale the amount + multiplier := new(big.Float).SetInt(new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(tokenDecimal)), nil)) + finalAmount := new(big.Float).Mul(amount, multiplier) + + // Convert finalAmount to a string + finalAmountStr := new(big.Int) + finalAmount.Int(finalAmountStr) + + return finalAmountStr.String(), nil +} + +func ReadTransactionData(ctx context.Context, store DataStore, sessionId string) (map[DataTyp][]byte, error) { + dataKeys := []DataTyp{ + DATA_TEMPORARY_VALUE, + DATA_ACTIVE_SYM, + DATA_AMOUNT, + DATA_PUBLIC_KEY, + DATA_RECIPIENT, + DATA_ACTIVE_DECIMAL, + DATA_ACTIVE_ADDRESS, + } + + data := make(map[DataTyp][]byte) + for _, key := range dataKeys { + value, err := store.ReadEntry(ctx, sessionId, key) + if err != nil { + return nil, err + } + data[key] = value + } + return data, nil +} diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index 00c6ad7..d2411bd 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "fmt" - "math/big" "path" "regexp" "strconv" @@ -1176,7 +1175,7 @@ func (h *Handlers) GetSender(ctx context.Context, sym string, input []byte) (res return res, fmt.Errorf("missing session") } - res.Content = string(sessionId) + res.Content = sessionId return res, nil } @@ -1205,8 +1204,7 @@ func (h *Handlers) GetAmount(ctx context.Context, sym string, input []byte) (res return res, nil } -// InitiateTransaction returns a confirmation and resets the transaction data -// on the gdbm store. +// InitiateTransaction calls the TokenTransfer and returns a confirmation based on the result func (h *Handlers) InitiateTransaction(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result var err error @@ -1215,57 +1213,44 @@ func (h *Handlers) InitiateTransaction(ctx context.Context, sym string, input [] return res, fmt.Errorf("missing session") } - flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") - account_authorized_flag, _ := h.flagManager.GetFlag("flag_account_authorized") + flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized") code := codeFromCtx(ctx) l := gotext.NewLocale(translationDir, code) l.AddDomain("default") - store := h.userdataStore - recipientNumber, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE) - activeSym, _ := store.ReadEntry(ctx, sessionId, common.DATA_ACTIVE_SYM) - - storedAmount, _ := store.ReadEntry(ctx, sessionId, common.DATA_AMOUNT) - fromAddress, _ := store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY) - toAddress, _ := store.ReadEntry(ctx, sessionId, common.DATA_RECIPIENT) - activeDecimal, _ := store.ReadEntry(ctx, sessionId, common.DATA_ACTIVE_DECIMAL) - tokenAddress, _ := store.ReadEntry(ctx, sessionId, common.DATA_ACTIVE_ADDRESS) - - // Parse tokendecimal - tokenDecimal, err := strconv.Atoi(string(activeDecimal)) + data, err := common.ReadTransactionData(ctx, h.userdataStore, sessionId) if err != nil { return res, err } - // Parse amount and scale it - amount, _, err := big.ParseFloat(string(storedAmount), 10, 0, big.ToZero) + finalAmountStr, err := common.ParseAndScaleAmount(data[common.DATA_AMOUNT], data[common.DATA_ACTIVE_DECIMAL]) if err != nil { return res, err } - multiplier := new(big.Float).SetInt(new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(tokenDecimal)), nil)) - finalAmount := new(big.Float).Mul(amount, multiplier) - - // Convert finalAmount to a string - finalAmountStr := new(big.Int) - finalAmount.Int(finalAmountStr) - // Call TokenTransfer - r, err := h.accountService.TokenTransfer(ctx, finalAmountStr.String(), string(fromAddress), string(toAddress), string(tokenAddress)) + r, err := h.accountService.TokenTransfer(ctx, finalAmountStr, string(data[common.DATA_PUBLIC_KEY]), string(data[common.DATA_RECIPIENT]), string(data[common.DATA_ACTIVE_ADDRESS])) if err != nil { - res.Content = l.Get("Your request failed. Please try again later.") + 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 TokenTransfer", "error", err) - return res, err + return res, nil } trackingId := r.TrackingId logg.InfoCtxf(ctx, "TokenTransfer", "trackingId", trackingId) - res.Content = l.Get("Your request has been sent. %s will receive %s %s from %s.", string(recipientNumber), string(storedAmount), string(activeSym), string(sessionId)) + res.Content = l.Get( + "Your request has been sent. %s will receive %s %s from %s.", + string(data[common.DATA_TEMPORARY_VALUE]), + string(data[common.DATA_AMOUNT]), + string(data[common.DATA_ACTIVE_SYM]), + sessionId, + ) - res.FlagReset = append(res.FlagReset, account_authorized_flag) + res.FlagReset = append(res.FlagReset, flag_account_authorized) return res, nil } From cb13b0929192f6f5ad91304d5f52736111bd214f Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Fri, 15 Nov 2024 08:42:44 +0300 Subject: [PATCH 10/24] remove commented code --- common/vouchers.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/common/vouchers.go b/common/vouchers.go index dd8852e..54ace28 100644 --- a/common/vouchers.go +++ b/common/vouchers.go @@ -37,15 +37,6 @@ func ProcessVouchers(holdings []dataserviceapi.TokenHoldings) VoucherMetadata { return data } -//func StoreVouchers(db storage.PrefixDb, data VoucherMetadata) { -// value, err := db.Put(ctx, []byte(key)) -// if err != nil { -// return nil, fmt.Errorf("failed to get %s: %v", key, err) -// } -// data[key] = string(value) -// } -//} - // 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"} From 1a0b4deab3aea25f7dbedb352fd6cf00af0b6476 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Fri, 15 Nov 2024 09:20:19 +0300 Subject: [PATCH 11/24] added the TransactionData struct to organiza the data --- common/tokens.go | 63 +++++++++++++++++++-------- internal/handlers/ussd/menuhandler.go | 10 ++--- 2 files changed, 50 insertions(+), 23 deletions(-) diff --git a/common/tokens.go b/common/tokens.go index 727cbea..466f370 100644 --- a/common/tokens.go +++ b/common/tokens.go @@ -2,20 +2,32 @@ package common import ( "context" + "errors" "math/big" + "reflect" "strconv" ) -func ParseAndScaleAmount(storedAmount, activeDecimal []byte) (string, error) { +type TransactionData struct { + TemporaryValue string + ActiveSym string + Amount string + PublicKey string + Recipient string + ActiveDecimal string + ActiveAddress string +} + +func ParseAndScaleAmount(storedAmount, activeDecimal string) (string, error) { // Parse token decimal - tokenDecimal, err := strconv.Atoi(string(activeDecimal)) + tokenDecimal, err := strconv.Atoi(activeDecimal) if err != nil { return "", err } // Parse amount - amount, _, err := big.ParseFloat(string(storedAmount), 10, 0, big.ToZero) + amount, _, err := big.ParseFloat(storedAmount, 10, 0, big.ToZero) if err != nil { return "", err } @@ -31,24 +43,39 @@ func ParseAndScaleAmount(storedAmount, activeDecimal []byte) (string, error) { return finalAmountStr.String(), nil } -func ReadTransactionData(ctx context.Context, store DataStore, sessionId string) (map[DataTyp][]byte, error) { - dataKeys := []DataTyp{ - DATA_TEMPORARY_VALUE, - DATA_ACTIVE_SYM, - DATA_AMOUNT, - DATA_PUBLIC_KEY, - DATA_RECIPIENT, - DATA_ACTIVE_DECIMAL, - DATA_ACTIVE_ADDRESS, +func ReadTransactionData(ctx context.Context, store DataStore, sessionId string) (TransactionData, error) { + data := TransactionData{} + fieldToKey := map[string]DataTyp{ + "TemporaryValue": DATA_TEMPORARY_VALUE, + "ActiveSym": DATA_ACTIVE_SYM, + "Amount": DATA_AMOUNT, + "PublicKey": DATA_PUBLIC_KEY, + "Recipient": DATA_RECIPIENT, + "ActiveDecimal": DATA_ACTIVE_DECIMAL, + "ActiveAddress": DATA_ACTIVE_ADDRESS, } - data := make(map[DataTyp][]byte) - for _, key := range dataKeys { - value, err := store.ReadEntry(ctx, sessionId, key) - if err != nil { - return nil, err + 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) } - data[key] = value + + value, err := readStringEntry(ctx, store, sessionId, key) + if err != nil { + return data, err + } + field.SetString(value) } + return data, nil } + +func readStringEntry(ctx context.Context, store DataStore, sessionId string, key DataTyp) (string, error) { + entry, err := store.ReadEntry(ctx, sessionId, key) + if err != nil { + return "", err + } + return string(entry), nil +} diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index d2411bd..0b9360f 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -1224,13 +1224,13 @@ func (h *Handlers) InitiateTransaction(ctx context.Context, sym string, input [] return res, err } - finalAmountStr, err := common.ParseAndScaleAmount(data[common.DATA_AMOUNT], data[common.DATA_ACTIVE_DECIMAL]) + finalAmountStr, err := common.ParseAndScaleAmount(data.Amount, data.ActiveDecimal) if err != nil { return res, err } // Call TokenTransfer - r, err := h.accountService.TokenTransfer(ctx, finalAmountStr, string(data[common.DATA_PUBLIC_KEY]), string(data[common.DATA_RECIPIENT]), string(data[common.DATA_ACTIVE_ADDRESS])) + r, err := h.accountService.TokenTransfer(ctx, finalAmountStr, data.PublicKey, data.Recipient, data.ActiveAddress) if err != nil { flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") res.FlagSet = append(res.FlagSet, flag_api_error) @@ -1244,9 +1244,9 @@ func (h *Handlers) InitiateTransaction(ctx context.Context, sym string, input [] res.Content = l.Get( "Your request has been sent. %s will receive %s %s from %s.", - string(data[common.DATA_TEMPORARY_VALUE]), - string(data[common.DATA_AMOUNT]), - string(data[common.DATA_ACTIVE_SYM]), + data.TemporaryValue, + data.Amount, + data.ActiveSym, sessionId, ) From 222d801ecc454d60c3eec4f31148ff2763b3aff5 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Fri, 15 Nov 2024 11:49:47 +0300 Subject: [PATCH 12/24] added TestParseAndScaleAmount --- common/tokens_test.go | 88 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 common/tokens_test.go diff --git a/common/tokens_test.go b/common/tokens_test.go new file mode 100644 index 0000000..5634ab9 --- /dev/null +++ b/common/tokens_test.go @@ -0,0 +1,88 @@ +package common + +import ( + "testing" +) + +func TestParseAndScaleAmount(t *testing.T) { + tests := []struct { + name string + amount string + decimals string + want string + expectError bool + }{ + { + name: "whole number", + amount: "123", + decimals: "2", + want: "12300", + expectError: false, + }, + { + name: "decimal number", + amount: "123.45", + decimals: "2", + want: "12345", + expectError: false, + }, + { + name: "zero decimals", + amount: "123.45", + decimals: "0", + want: "123", + expectError: false, + }, + { + name: "large number", + amount: "1000000.01", + decimals: "6", + want: "1000000010000", + expectError: false, + }, + { + name: "invalid amount", + amount: "abc", + decimals: "2", + want: "", + expectError: true, + }, + { + name: "invalid decimals", + amount: "123.45", + decimals: "abc", + want: "", + expectError: true, + }, + { + name: "zero amount", + amount: "0", + decimals: "2", + want: "0", + expectError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseAndScaleAmount(tt.amount, tt.decimals) + + // Check error cases + if tt.expectError { + if err == nil { + t.Errorf("ParseAndScaleAmount(%q, %q) expected error, got nil", tt.amount, tt.decimals) + } + return + } + + if err != nil { + t.Errorf("ParseAndScaleAmount(%q, %q) unexpected error: %v", tt.amount, tt.decimals, err) + return + } + + if got != tt.want { + t.Errorf("ParseAndScaleAmount(%q, %q) = %v, want %v", tt.amount, tt.decimals, got, tt.want) + } + }) + } +} From 36846c2587c830332d3d528af62c2e828b6b0a52 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Fri, 15 Nov 2024 12:13:09 +0300 Subject: [PATCH 13/24] added TestReadTransactionData --- common/tokens_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/common/tokens_test.go b/common/tokens_test.go index 5634ab9..06bd552 100644 --- a/common/tokens_test.go +++ b/common/tokens_test.go @@ -2,6 +2,8 @@ package common import ( "testing" + + "github.com/alecthomas/assert/v2" ) func TestParseAndScaleAmount(t *testing.T) { @@ -86,3 +88,42 @@ func TestParseAndScaleAmount(t *testing.T) { }) } } + +func TestReadTransactionData(t *testing.T) { + sessionId := "session123" + publicKey := "0X13242618721" + ctx, store := InitializeTestDb(t) + + // Test transaction data + transactionData := map[DataTyp]string{ + DATA_TEMPORARY_VALUE: "0712345678", + DATA_ACTIVE_SYM: "SRF", + DATA_AMOUNT: "1000000", + DATA_PUBLIC_KEY: publicKey, + DATA_RECIPIENT: "0x41c188d63Qa", + DATA_ACTIVE_DECIMAL: "6", + DATA_ACTIVE_ADDRESS: "0xd4c288865Ce", + } + + // Store the data + for key, value := range transactionData { + if err := store.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil { + t.Fatal(err) + } + } + + expectedResult := TransactionData{ + TemporaryValue: "0712345678", + ActiveSym: "SRF", + Amount: "1000000", + PublicKey: publicKey, + Recipient: "0x41c188d63Qa", + ActiveDecimal: "6", + ActiveAddress: "0xd4c288865Ce", + } + + data, err := ReadTransactionData(ctx, store, sessionId) + + assert.NoError(t, err) + assert.Equal(t, expectedResult, data) +} From 1a77092ccbfd25d02d66c801cdb9166efcb889d1 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Fri, 15 Nov 2024 13:12:30 +0300 Subject: [PATCH 14/24] updated the menuhandler tests --- internal/handlers/ussd/menuhandler_test.go | 97 ++++++++++++++++------ 1 file changed, 72 insertions(+), 25 deletions(-) diff --git a/internal/handlers/ussd/menuhandler_test.go b/internal/handlers/ussd/menuhandler_test.go index f4c9f7c..ff6f0ad 100644 --- a/internal/handlers/ussd/menuhandler_test.go +++ b/internal/handlers/ussd/menuhandler_test.go @@ -586,9 +586,9 @@ func TestGetRecipient(t *testing.T) { ctx, store := InitializeTestStore(t) ctx = context.WithValue(ctx, "SessionId", sessionId) - recepient := "0xcasgatweksalw1018221" + recepient := "0712345678" - err := store.WriteEntry(ctx, sessionId, common.DATA_RECIPIENT, []byte(recepient)) + err := store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(recepient)) if err != nil { t.Fatal(err) } @@ -1226,32 +1226,41 @@ func TestInitiateTransaction(t *testing.T) { } tests := []struct { - name string - input []byte - Recipient []byte - Amount []byte - ActiveSym []byte - status string - expectedResult resource.Result + name string + TemporaryValue []byte + ActiveSym []byte + StoredAmount []byte + TransferAmount string + PublicKey []byte + Recipient []byte + ActiveDecimal []byte + ActiveAddress []byte + TransferResponse *models.TokenTransferResponse + expectedResult resource.Result }{ { - name: "Test initiate transaction", - Amount: []byte("0.002"), - ActiveSym: []byte("SRF"), - Recipient: []byte("0x12415ass27192"), + name: "Test initiate transaction", + TemporaryValue: []byte("0711223344"), + ActiveSym: []byte("SRF"), + StoredAmount: []byte("1.00"), + TransferAmount: "1000000", + PublicKey: []byte("0X13242618721"), + Recipient: []byte("0x12415ass27192"), + ActiveDecimal: []byte("6"), + ActiveAddress: []byte("0xd4c288865Ce"), + TransferResponse: &models.TokenTransferResponse{ + TrackingId: "1234567890", + }, expectedResult: resource.Result{ FlagReset: []uint32{account_authorized_flag}, - Content: "Your request has been sent. 0x12415ass27192 will receive 0.002 SRF from 254712345678.", + Content: "Your request has been sent. 0711223344 will receive 1.00 SRF from 254712345678.", }, }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := store.WriteEntry(ctx, sessionId, common.DATA_AMOUNT, []byte(tt.Amount)) - if err != nil { - t.Fatal(err) - } - err = store.WriteEntry(ctx, sessionId, common.DATA_RECIPIENT, []byte(tt.Recipient)) + err := store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(tt.TemporaryValue)) if err != nil { t.Fatal(err) } @@ -1259,9 +1268,31 @@ func TestInitiateTransaction(t *testing.T) { if err != nil { t.Fatal(err) } + err = store.WriteEntry(ctx, sessionId, common.DATA_AMOUNT, []byte(tt.StoredAmount)) + if err != nil { + t.Fatal(err) + } + err = store.WriteEntry(ctx, sessionId, common.DATA_PUBLIC_KEY, []byte(tt.PublicKey)) + if err != nil { + t.Fatal(err) + } + err = store.WriteEntry(ctx, sessionId, common.DATA_RECIPIENT, []byte(tt.Recipient)) + if err != nil { + t.Fatal(err) + } + err = store.WriteEntry(ctx, sessionId, common.DATA_ACTIVE_DECIMAL, []byte(tt.ActiveDecimal)) + if err != nil { + t.Fatal(err) + } + err = store.WriteEntry(ctx, sessionId, common.DATA_ACTIVE_ADDRESS, []byte(tt.ActiveAddress)) + if err != nil { + t.Fatal(err) + } + + mockAccountService.On("TokenTransfer").Return(tt.TransferResponse, nil) // Call the method under test - res, _ := h.InitiateTransaction(ctx, "transaction_reset_amount", tt.input) + res, _ := h.InitiateTransaction(ctx, "transaction_reset_amount", []byte("")) // Assert that no errors occurred assert.NoError(t, err) @@ -1453,10 +1484,12 @@ func TestValidateRecipient(t *testing.T) { } sessionId := "session123" + publicKey := "0X13242618721" ctx, store := InitializeTestStore(t) ctx = context.WithValue(ctx, "SessionId", sessionId) flag_invalid_recipient, _ := fm.parser.GetFlag("flag_invalid_recipient") + flag_invalid_recipient_with_invite, _ := fm.parser.GetFlag("flag_invalid_recipient_with_invite") // Define test cases tests := []struct { @@ -1466,19 +1499,33 @@ func TestValidateRecipient(t *testing.T) { }{ { name: "Test with invalid recepient", - input: []byte("000"), + input: []byte("9234adf5"), expectedResult: resource.Result{ FlagSet: []uint32{flag_invalid_recipient}, - Content: "000", + Content: "9234adf5", }, }, { - name: "Test with valid recepient", - input: []byte("0705X2"), + name: "Test with valid unregistered recepient", + input: []byte("0712345678"), + expectedResult: resource.Result{ + FlagSet: []uint32{flag_invalid_recipient_with_invite}, + Content: "0712345678", + }, + }, + { + name: "Test with valid registered recepient", + input: []byte("0711223344"), expectedResult: resource.Result{}, }, } + // store a public key for the valid recipient + err = store.WriteEntry(ctx, "0711223344", common.DATA_PUBLIC_KEY, []byte(publicKey)) + if err != nil { + t.Fatal(err) + } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Create the Handlers instance @@ -1987,7 +2034,7 @@ func TestSetVoucher(t *testing.T) { t.Fatal(err) } - res, err := h.SetVoucher(ctx, "set_voucher", []byte{}) + res, err := h.SetVoucher(ctx, "set_voucher", []byte("")) assert.NoError(t, err) From d3fae34290049dc67d00f24575439b2f89b2fd4a Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Fri, 15 Nov 2024 13:24:54 +0300 Subject: [PATCH 15/24] modified the send node traversal --- menutraversal_test/test_setup.json | 32 ++++++------------------------ 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/menutraversal_test/test_setup.json b/menutraversal_test/test_setup.json index 3acb889..a554939 100644 --- a/menutraversal_test/test_setup.json +++ b/menutraversal_test/test_setup.json @@ -53,7 +53,7 @@ ] }, { - "name": "send_with_invalid_inputs", + "name": "send_with_invite", "steps": [ { "input": "", @@ -65,39 +65,19 @@ }, { "input": "000", - "expectedContent": "000 is not registered or invalid, please try again:\n1:Retry\n9:Quit" + "expectedContent": "000 is invalid, please try again:\n1:Retry\n9:Quit" }, { "input": "1", "expectedContent": "Enter recipient's phone number:\n0:Back" }, { - "input": "065656", - "expectedContent": "{max_amount}\nEnter amount:\n0:Back" + "input": "0712345678", + "expectedContent": "0712345678 is not registered, please try again:\n1:Retry\n2:Invite to Sarafu Network\n9:Quit" }, { - "input": "10000000", - "expectedContent": "Amount 10000000 is invalid, please try again:\n1:Retry\n9:Quit" - }, - { - "input": "1", - "expectedContent": "{max_amount}\nEnter amount:\n0:Back" - }, - { - "input": "1.00", - "expectedContent": "065656 will receive {send_amount} from {session_id}\nPlease enter your PIN to confirm:\n0:Back\n9:Quit" - }, - { - "input": "1222", - "expectedContent": "Incorrect pin\n1:Retry\n9:Quit" - }, - { - "input": "1", - "expectedContent": "065656 will receive {send_amount} from {session_id}\nPlease enter your PIN to confirm:\n0:Back\n9:Quit" - }, - { - "input": "1234", - "expectedContent": "Your request has been sent. 065656 will receive {send_amount} from {session_id}." + "input": "2", + "expectedContent": "Your invite request for 0712345678 to Sarafu Network failed. Please try again later." } ] }, From 51bf2534b87d1398202e8ec85410b1d21a95dd60 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Fri, 15 Nov 2024 18:35:49 +0300 Subject: [PATCH 16/24] use a single bearer token --- .env.example | 3 +-- config/config.go | 10 ++++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/.env.example b/.env.example index d392c9b..c636fa8 100644 --- a/.env.example +++ b/.env.example @@ -16,6 +16,5 @@ DB_TIMEZONE=Africa/Nairobi #External API Calls CUSTODIAL_URL_BASE=http://localhost:5003 -CUSTODIAL_BEARER_TOKEN=eyJeSIsInRcCI6IkpXVCJ.yJwdWJsaWNLZXkiOiIwrrrrrr +BEARER_TOKEN=eyJeSIsInRcCI6IkpXVCJ.yJwdWJsaWNLZXkiOiIwrrrrrr DATA_URL_BASE=http://localhost:5006 -DATA_BEARER_TOKEN=eyJeSIsIRcCI6IXVCJ.yJwdWJsaLZXkiOiIwrrrrrr diff --git a/config/config.go b/config/config.go index 0d39e6e..edb33fe 100644 --- a/config/config.go +++ b/config/config.go @@ -18,10 +18,9 @@ const ( ) var ( - custodialURLBase string - dataURLBase string - CustodialBearerToken string - DataBearerToken string + custodialURLBase string + dataURLBase string + BearerToken string ) var ( @@ -40,8 +39,7 @@ func setBase() error { custodialURLBase = initializers.GetEnv("CUSTODIAL_URL_BASE", "http://localhost:5003") dataURLBase = initializers.GetEnv("DATA_URL_BASE", "http://localhost:5006") - CustodialBearerToken = initializers.GetEnv("CUSTODIAL_BEARER_TOKEN", "") - DataBearerToken = initializers.GetEnv("DATA_BEARER_TOKEN", "") + BearerToken = initializers.GetEnv("BEARER_TOKEN", "") _, err = url.JoinPath(custodialURLBase, "/foo") if err != nil { From baeb5e0ccba55d937a985e4b5313ba573bfe4850 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Fri, 15 Nov 2024 18:37:20 +0300 Subject: [PATCH 17/24] use a single doRequest and updated the structs for TokenHoldings and Transfers --- remote/accountservice.go | 43 +++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/remote/accountservice.go b/remote/accountservice.go index 3af1ab1..6c94a28 100644 --- a/remote/accountservice.go +++ b/remote/accountservice.go @@ -51,7 +51,7 @@ func (as *AccountService) TrackAccountStatus(ctx context.Context, publicKey stri return nil, err } - _, err = doCustodialRequest(ctx, req, &r) + _, err = doRequest(ctx, req, &r) if err != nil { return nil, err } @@ -75,7 +75,7 @@ func (as *AccountService) CheckBalance(ctx context.Context, publicKey string) (* return nil, err } - _, err = doCustodialRequest(ctx, req, &balanceResult) + _, err = doRequest(ctx, req, &balanceResult) return &balanceResult, err } @@ -92,7 +92,7 @@ func (as *AccountService) CreateAccount(ctx context.Context) (*models.AccountRes if err != nil { return nil, err } - _, err = doCustodialRequest(ctx, req, &r) + _, err = doRequest(ctx, req, &r) if err != nil { return nil, err } @@ -104,7 +104,9 @@ func (as *AccountService) CreateAccount(ctx context.Context) (*models.AccountRes // 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 + var r struct { + Holdings []dataserviceapi.TokenHoldings `json:"holdings"` + } ep, err := url.JoinPath(config.VoucherHoldingsURL, publicKey) if err != nil { @@ -116,19 +118,21 @@ func (as *AccountService) FetchVouchers(ctx context.Context, publicKey string) ( return nil, err } - _, err = doDataRequest(ctx, req, r) + _, err = doRequest(ctx, req, &r) if err != nil { return nil, err } - return r, nil + return r.Holdings, nil } // FetchTransactions retrieves the last 10 transactions for a given public key from the data indexer API endpoint // Parameters: // - publicKey: The public key associated with the account. func (as *AccountService) FetchTransactions(ctx context.Context, publicKey string) ([]dataserviceapi.Last10TxResponse, error) { - var r []dataserviceapi.Last10TxResponse + var r struct { + Transfers []dataserviceapi.Last10TxResponse `json:"transfers"` + } ep, err := url.JoinPath(config.VoucherTransfersURL, publicKey) if err != nil { @@ -140,12 +144,12 @@ func (as *AccountService) FetchTransactions(ctx context.Context, publicKey strin return nil, err } - _, err = doDataRequest(ctx, req, r) + _, err = doRequest(ctx, req, &r) if err != nil { return nil, err } - return r, nil + return r.Transfers, nil } // VoucherData retrieves voucher metadata from the data indexer API endpoint. @@ -164,7 +168,7 @@ func (as *AccountService) VoucherData(ctx context.Context, address string) (*mod return nil, err } - _, err = doCustodialRequest(ctx, req, &voucherDataResult) + _, err = doRequest(ctx, req, &voucherDataResult) return &voucherDataResult, err } @@ -195,7 +199,7 @@ func (as *AccountService) TokenTransfer(ctx context.Context, amount, from, to, t if err != nil { return nil, err } - _, err = doCustodialRequest(ctx, req, &r) + _, err = doRequest(ctx, req, &r) if err != nil { return nil, err } @@ -206,7 +210,12 @@ func (as *AccountService) TokenTransfer(ctx context.Context, amount, from, to, t func doRequest(ctx context.Context, req *http.Request, rcpt any) (*api.OKResponse, error) { var okResponse api.OKResponse var errResponse api.ErrResponse + + req.Header.Set("Authorization", "Bearer "+config.BearerToken) req.Header.Set("Content-Type", "application/json") + + logRequestDetails(req) + resp, err := http.DefaultClient.Do(req) if err != nil { log.Printf("Failed to make %s request to endpoint: %s with reason: %s", req.Method, req.URL, err.Error()) @@ -244,18 +253,6 @@ func doRequest(ctx context.Context, req *http.Request, rcpt any) (*api.OKRespons return &okResponse, err } -func doCustodialRequest(ctx context.Context, req *http.Request, rcpt any) (*api.OKResponse, error) { - req.Header.Set("Authorization", "Bearer "+config.CustodialBearerToken) - logRequestDetails(req) - return doRequest(ctx, req, rcpt) -} - -func doDataRequest(ctx context.Context, req *http.Request, rcpt any) (*api.OKResponse, error) { - req.Header.Set("Authorization", "Bearer "+config.DataBearerToken) - logRequestDetails(req) - return doRequest(ctx, req, rcpt) -} - func logRequestDetails(req *http.Request) { var bodyBytes []byte contentType := req.Header.Get("Content-Type") From c8fc32a4e7a618c5a55ddb96536251cfa0265a8e Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Fri, 15 Nov 2024 18:50:06 +0300 Subject: [PATCH 18/24] cleaned up models --- models/accountresponse.go | 2 +- models/balanceresponse.go | 1 - models/tokenresponse.go | 18 ------------------ models/trackstatusresponse.go | 2 +- models/voucher_data_result.go | 8 ++++++++ models/vouchersresponse.go | 21 --------------------- 6 files changed, 10 insertions(+), 42 deletions(-) delete mode 100644 models/tokenresponse.go create mode 100644 models/voucher_data_result.go delete mode 100644 models/vouchersresponse.go diff --git a/models/accountresponse.go b/models/accountresponse.go index 278e0e9..dc8e758 100644 --- a/models/accountresponse.go +++ b/models/accountresponse.go @@ -1,6 +1,6 @@ package models type AccountResult struct { - PublicKey string `json:"publicKey"` + PublicKey string `json:"publicKey"` TrackingId string `json:"trackingId"` } diff --git a/models/balanceresponse.go b/models/balanceresponse.go index b2baa41..88e9ce9 100644 --- a/models/balanceresponse.go +++ b/models/balanceresponse.go @@ -2,7 +2,6 @@ package models import "encoding/json" - type BalanceResult struct { Balance string `json:"balance"` Nonce json.Number `json:"nonce"` diff --git a/models/tokenresponse.go b/models/tokenresponse.go deleted file mode 100644 index d243d93..0000000 --- a/models/tokenresponse.go +++ /dev/null @@ -1,18 +0,0 @@ -package models - -type ApiResponse struct { - OK bool `json:"ok"` - Description string `json:"description"` - Result Result `json:"result"` -} - -type Result struct { - Holdings []Holding `json:"holdings"` -} - -type Holding struct { - ContractAddress string `json:"contractAddress"` - TokenSymbol string `json:"tokenSymbol"` - TokenDecimals string `json:"tokenDecimals"` - Balance string `json:"balance"` -} diff --git a/models/trackstatusresponse.go b/models/trackstatusresponse.go index 47d757d..0c3c230 100644 --- a/models/trackstatusresponse.go +++ b/models/trackstatusresponse.go @@ -14,5 +14,5 @@ type Transaction struct { } type TrackStatusResult struct { - Active bool `json:"active"` + Active bool `json:"active"` } diff --git a/models/voucher_data_result.go b/models/voucher_data_result.go new file mode 100644 index 0000000..f04aafe --- /dev/null +++ b/models/voucher_data_result.go @@ -0,0 +1,8 @@ +package models + +type VoucherDataResult struct { + TokenName string `json:"tokenName"` + TokenSymbol string `json:"tokenSymbol"` + TokenDecimals string `json:"tokenDecimals"` + SinkAddress string `json:"sinkAddress"` +} diff --git a/models/vouchersresponse.go b/models/vouchersresponse.go deleted file mode 100644 index 8cf3ec6..0000000 --- a/models/vouchersresponse.go +++ /dev/null @@ -1,21 +0,0 @@ -package models - -import dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" - -//type VoucherHoldingResponse struct { -// Ok bool `json:"ok"` -// Description string `json:"description"` -// Result VoucherResult `json:"result"` -//} - -// VoucherResult holds the list of token holdings -type VoucherResult struct { - Holdings []dataserviceapi.TokenHoldings `json:"holdings"` -} - -type VoucherDataResult struct { - TokenName string `json:"tokenName"` - TokenSymbol string `json:"tokenSymbol"` - TokenDecimals string `json:"tokenDecimals"` - SinkAddress string `json:"sinkAddress"` -} From 7d16b710d81f31c78480a0a4cf101c1e42dfd8b4 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Fri, 15 Nov 2024 20:50:07 +0300 Subject: [PATCH 19/24] set the TokenDecimals as int to match the API response --- models/voucher_data_result.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/voucher_data_result.go b/models/voucher_data_result.go index f04aafe..c9f0b74 100644 --- a/models/voucher_data_result.go +++ b/models/voucher_data_result.go @@ -3,6 +3,6 @@ package models type VoucherDataResult struct { TokenName string `json:"tokenName"` TokenSymbol string `json:"tokenSymbol"` - TokenDecimals string `json:"tokenDecimals"` + TokenDecimals int `json:"tokenDecimals"` SinkAddress string `json:"sinkAddress"` } From c0ccdce0a9e2d2a574a55b223b5ebab6d2008299 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Fri, 15 Nov 2024 21:02:44 +0300 Subject: [PATCH 20/24] add the voucher details node --- services/registration/my_vouchers.vis | 1 + services/registration/voucher_details | 1 + services/registration/voucher_details.vis | 6 ++++++ services/registration/voucher_details_swa | 1 + 4 files changed, 9 insertions(+) create mode 100644 services/registration/voucher_details create mode 100644 services/registration/voucher_details.vis create mode 100644 services/registration/voucher_details_swa diff --git a/services/registration/my_vouchers.vis b/services/registration/my_vouchers.vis index b59441a..e79438e 100644 --- a/services/registration/my_vouchers.vis +++ b/services/registration/my_vouchers.vis @@ -6,3 +6,4 @@ MOUT back 0 HALT INCMP _ 0 INCMP select_voucher 1 +INCMP voucher_details 2 diff --git a/services/registration/voucher_details b/services/registration/voucher_details new file mode 100644 index 0000000..d437681 --- /dev/null +++ b/services/registration/voucher_details @@ -0,0 +1 @@ +{{.get_voucher_details}} \ No newline at end of file diff --git a/services/registration/voucher_details.vis b/services/registration/voucher_details.vis new file mode 100644 index 0000000..1b009f1 --- /dev/null +++ b/services/registration/voucher_details.vis @@ -0,0 +1,6 @@ +CATCH no_voucher flag_no_active_voucher 1 +LOAD get_voucher_details 0 +MAP get_voucher_details +MOUT back 0 +HALT +INCMP _ 0 diff --git a/services/registration/voucher_details_swa b/services/registration/voucher_details_swa new file mode 100644 index 0000000..d437681 --- /dev/null +++ b/services/registration/voucher_details_swa @@ -0,0 +1 @@ +{{.get_voucher_details}} \ No newline at end of file From 11bb194f264d22eeda652035f744a77bfad9e325 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Fri, 15 Nov 2024 21:03:29 +0300 Subject: [PATCH 21/24] add a struct to access the tokenDetails --- remote/accountservice.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/remote/accountservice.go b/remote/accountservice.go index 6c94a28..23b62ca 100644 --- a/remote/accountservice.go +++ b/remote/accountservice.go @@ -156,7 +156,9 @@ func (as *AccountService) FetchTransactions(ctx context.Context, publicKey strin // Parameters: // - address: The voucher address. func (as *AccountService) VoucherData(ctx context.Context, address string) (*models.VoucherDataResult, error) { - var voucherDataResult models.VoucherDataResult + var r struct { + TokenDetails models.VoucherDataResult `json:"tokenDetails"` + } ep, err := url.JoinPath(config.VoucherDataURL, address) if err != nil { @@ -168,8 +170,8 @@ func (as *AccountService) VoucherData(ctx context.Context, address string) (*mod return nil, err } - _, err = doRequest(ctx, req, &voucherDataResult) - return &voucherDataResult, err + _, err = doRequest(ctx, req, &r) + return &r.TokenDetails, err } // TokenTransfer creates a new token transfer in the custodial system. From b40ad782949f682e02eb90f6347e6c122476a8f1 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Fri, 15 Nov 2024 21:03:57 +0300 Subject: [PATCH 22/24] add the GetVoucherDetails function --- internal/handlers/handlerservice.go | 1 + internal/handlers/ussd/menuhandler.go | 33 +++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/internal/handlers/handlerservice.go b/internal/handlers/handlerservice.go index 358d492..e0cad8f 100644 --- a/internal/handlers/handlerservice.go +++ b/internal/handlers/handlerservice.go @@ -109,6 +109,7 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountServiceIn ls.DbRs.AddLocalFunc("get_vouchers", ussdHandlers.GetVoucherList) ls.DbRs.AddLocalFunc("view_voucher", ussdHandlers.ViewVoucher) ls.DbRs.AddLocalFunc("set_voucher", ussdHandlers.SetVoucher) + ls.DbRs.AddLocalFunc("get_voucher_details", ussdHandlers.GetVoucherDetails) ls.DbRs.AddLocalFunc("reset_valid_pin", ussdHandlers.ResetValidPin) ls.DbRs.AddLocalFunc("check_pin_mismatch", ussdHandlers.CheckPinMisMatch) ls.DbRs.AddLocalFunc("validate_blocked_number", ussdHandlers.ValidateBlockedNumber) diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index 0b9360f..8a3e8cf 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -1624,3 +1624,36 @@ func (h *Handlers) SetVoucher(ctx context.Context, sym string, input []byte) (re res.Content = tempData.TokenSymbol return res, nil } + +// GetVoucherDetails retrieves the voucher details +func (h *Handlers) GetVoucherDetails(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + store := h.userdataStore + sessionId, ok := ctx.Value("SessionId").(string) + if !ok { + return res, fmt.Errorf("missing session") + } + + flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") + + // get the active address + activeAddress, err := store.ReadEntry(ctx, sessionId, common.DATA_ACTIVE_ADDRESS) + if err != nil { + logg.ErrorCtxf(ctx, "failed to read activeAddress entry with", "key", common.DATA_ACTIVE_ADDRESS, "error", err) + return res, err + } + + // use the voucher contract address to get the data from the API + voucherData, err := h.accountService.VoucherData(ctx, string(activeAddress)) + if err != nil { + res.FlagSet = append(res.FlagSet, flag_api_error) + return res, nil + } + + tokenSymbol := voucherData.TokenSymbol + tokenName := voucherData.TokenName + + res.Content = fmt.Sprintf("%s %s", tokenSymbol, tokenName) + + return res, nil +} From 5dd4f2a3fbba946c5d16d710a5dd8e17dd68e9a9 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Sat, 16 Nov 2024 14:52:02 +0300 Subject: [PATCH 23/24] scale down the balance according to the decimals --- common/vouchers.go | 29 +++++++++++++++++++++++++-- internal/handlers/ussd/menuhandler.go | 8 ++++++-- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/common/vouchers.go b/common/vouchers.go index 54ace28..81ca55f 100644 --- a/common/vouchers.go +++ b/common/vouchers.go @@ -3,6 +3,7 @@ package common import ( "context" "fmt" + "math/big" "strings" "git.grassecon.net/urdt/ussd/internal/storage" @@ -24,7 +25,11 @@ func ProcessVouchers(holdings []dataserviceapi.TokenHoldings) VoucherMetadata { 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)) + + // Scale down the balance + scaledBalance := ScaleDownBalance(h.Balance, h.TokenDecimals) + + balances = append(balances, fmt.Sprintf("%d:%s", i+1, scaledBalance)) decimals = append(decimals, fmt.Sprintf("%d:%s", i+1, h.TokenDecimals)) addresses = append(addresses, fmt.Sprintf("%d:%s", i+1, h.ContractAddress)) } @@ -37,6 +42,26 @@ func ProcessVouchers(holdings []dataserviceapi.TokenHoldings) VoucherMetadata { return data } +func ScaleDownBalance(balance, decimals string) string { + // Convert balance and decimals to big.Float + bal := new(big.Float) + bal.SetString(balance) + + dec, ok := new(big.Int).SetString(decimals, 10) + if !ok { + dec = big.NewInt(0) // Default to 0 decimals in case of conversion failure + } + + divisor := new(big.Float).SetInt(new(big.Int).Exp(big.NewInt(10), dec, nil)) + scaledBalance := new(big.Float).Quo(bal, divisor) + + // Return the scaled balance without trailing decimals if it's an integer + if scaledBalance.IsInt() { + return scaledBalance.Text('f', 0) + } + return scaledBalance.Text('f', -1) +} + // 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"} @@ -75,7 +100,7 @@ func MatchVoucher(input, symbols, balances, decimals, addresses string) (symbol, decList := strings.Split(decimals, "\n") addrList := strings.Split(addresses, "\n") - logg.Tracef("found" , "symlist", symList, "syms", symbols, "input", input) + logg.Tracef("found", "symlist", symList, "syms", symbols, "input", input) for i, sym := range symList { parts := strings.SplitN(sym, ":", 2) diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index 8a3e8cf..f2d3173 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -1468,6 +1468,9 @@ func (h *Handlers) SetDefaultVoucher(ctx context.Context, sym string, input []by defaultDec := firstVoucher.TokenDecimals defaultAddr := firstVoucher.ContractAddress + // Scale down the balance + scaledBalance := common.ScaleDownBalance(defaultBal, defaultDec) + // set the active symbol err = store.WriteEntry(ctx, sessionId, common.DATA_ACTIVE_SYM, []byte(defaultSym)) if err != nil { @@ -1475,9 +1478,9 @@ func (h *Handlers) SetDefaultVoucher(ctx context.Context, sym string, input []by return res, err } // set the active balance - err = store.WriteEntry(ctx, sessionId, common.DATA_ACTIVE_BAL, []byte(defaultBal)) + err = store.WriteEntry(ctx, sessionId, common.DATA_ACTIVE_BAL, []byte(scaledBalance)) if err != nil { - logg.ErrorCtxf(ctx, "failed to write defaultBal entry with", "key", common.DATA_ACTIVE_BAL, "value", defaultBal, "error", err) + logg.ErrorCtxf(ctx, "failed to write defaultBal entry with", "key", common.DATA_ACTIVE_BAL, "value", scaledBalance, "error", err) return res, err } // set the active decimals @@ -1563,6 +1566,7 @@ func (h *Handlers) GetVoucherList(ctx context.Context, sym string, input []byte) } // ViewVoucher retrieves the token holding and balance from the subprefixDB +// and displays it to the user for them to select it func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result sessionId, ok := ctx.Value("SessionId").(string) From 1ba90a8b780bf109c68aad4e31fbefb616787e95 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Sat, 16 Nov 2024 14:52:24 +0300 Subject: [PATCH 24/24] updated the test --- common/vouchers_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/vouchers_test.go b/common/vouchers_test.go index 8b9fa2a..9f5bf85 100644 --- a/common/vouchers_test.go +++ b/common/vouchers_test.go @@ -59,13 +59,13 @@ func TestMatchVoucher(t *testing.T) { func TestProcessVouchers(t *testing.T) { holdings := []dataserviceapi.TokenHoldings{ - {ContractAddress: "0xd4c288865Ce", TokenSymbol: "SRF", TokenDecimals: "6", Balance: "100"}, - {ContractAddress: "0x41c188d63Qa", TokenSymbol: "MILO", TokenDecimals: "4", Balance: "200"}, + {ContractAddress: "0xd4c288865Ce", TokenSymbol: "SRF", TokenDecimals: "6", Balance: "100000000"}, + {ContractAddress: "0x41c188d63Qa", TokenSymbol: "MILO", TokenDecimals: "4", Balance: "200000000"}, } expectedResult := VoucherMetadata{ Symbols: "1:SRF\n2:MILO", - Balances: "1:100\n2:200", + Balances: "1:100\n2:20000", Decimals: "1:6\n2:4", Addresses: "1:0xd4c288865Ce\n2:0x41c188d63Qa", }