added the grassrootseconomics/go-vise package to sarafu-api #16

Merged
Alfred-mk merged 2 commits from update-go-vise into master 2025-11-19 11:22:32 +01:00
7 changed files with 200 additions and 44 deletions
Showing only changes of commit 74e5fb016c - Show all commits

View File

@ -7,25 +7,27 @@ import (
)
const (
createAccountPath = "/api/v2/account/create"
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"
SendSMSPrefix = "api/v1/external/upsell"
poolDepositPrefix = "/api/v2/pool/deposit"
poolSwapQoutePrefix = "/api/v2/pool/quote"
poolSwapPrefix = "/api/v2/pool/swap"
topPoolsPrefix = "/api/v1/pool/top"
retrievePoolDetailsPrefix = "/api/v1/pool/reverse"
poolSwappableVouchersPrefix = "/api/v1/pool"
AliasRegistrationPrefix = "/api/v1/internal/register"
AliasResolverPrefix = "/api/v1/resolve"
ExternalSMSPrefix = "/api/v1/external"
AliasUpdatePrefix = "/api/v1/internal/update"
createAccountPath = "/api/v2/account/create"
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"
SendSMSPrefix = "api/v1/external/upsell"
poolDepositPrefix = "/api/v2/pool/deposit"
poolSwapQoutePrefix = "/api/v2/pool/quote"
poolSwapPrefix = "/api/v2/pool/swap"
topPoolsPrefix = "/api/v1/pool/top"
retrievePoolDetailsPrefix = "/api/v1/pool/reverse"
poolSwappableVouchersPrefix = "/api/v1/pool"
AliasRegistrationPrefix = "/api/v1/internal/register"
AliasResolverPrefix = "/api/v1/resolve"
ExternalSMSPrefix = "/api/v1/external"
AliasUpdatePrefix = "/api/v1/internal/update"
CreditSendPrefix = "/api/v1/credit-send"
CreditSendReverseQuotePrefix = "/api/v1/pool/reverse-quote"
)
var (
@ -38,25 +40,27 @@ var (
)
var (
CreateAccountURL string
TrackStatusURL string
BalanceURL string
TrackURL string
TokenTransferURL string
VoucherHoldingsURL string
VoucherTransfersURL string
VoucherDataURL string
PoolDepositURL string
PoolSwapQuoteURL string
PoolSwapURL string
TopPoolsURL string
RetrievePoolDetailsURL string
PoolSwappableVouchersURL string
SendSMSURL string
AliasRegistrationURL string
AliasResolverURL string
ExternalSMSURL string
AliasUpdateURL string
CreateAccountURL string
TrackStatusURL string
BalanceURL string
TrackURL string
TokenTransferURL string
VoucherHoldingsURL string
VoucherTransfersURL string
VoucherDataURL string
PoolDepositURL string
PoolSwapQuoteURL string
PoolSwapURL string
TopPoolsURL string
RetrievePoolDetailsURL string
PoolSwappableVouchersURL string
SendSMSURL string
AliasRegistrationURL string
AliasResolverURL string
ExternalSMSURL string
AliasUpdateURL string
CreditSendURL string
CreditSendReverseQuoteURL string
)
func setBase() error {
@ -105,6 +109,8 @@ func LoadConfig() error {
AliasResolverURL, _ = url.JoinPath(aliasEnsURLBase, AliasResolverPrefix)
ExternalSMSURL, _ = url.JoinPath(externalSMSBase, ExternalSMSPrefix)
AliasUpdateURL, _ = url.JoinPath(aliasEnsURLBase, AliasUpdatePrefix)
CreditSendURL, _ = url.JoinPath(dataURLBase, CreditSendPrefix)
CreditSendReverseQuoteURL, _ = url.JoinPath(dataURLBase, CreditSendReverseQuotePrefix)
return nil
}

View File

@ -907,3 +907,17 @@ func (das *DevAccountService) CheckTokenInPool(ctx context.Context, poolAddress,
CanSwapFrom: true,
}, nil
}
func (das *DevAccountService) GetCreditSendMaxLimit(ctx context.Context, poolAddress, fromTokenAddress, toTokenAddress, publicKey string) (*models.CreditSendLimitsResult, error) {
return &models.CreditSendLimitsResult{
MaxRAT: "45599996",
MaxSAT: "3507692",
}, nil
}
func (das *DevAccountService) GetCreditSendReverseQuote(ctx context.Context, poolAddress, fromTokenAddress, toTokenAddress, toTokenAMount string) (*models.CreditSendReverseQouteResult, error) {
return &models.CreditSendReverseQouteResult{
InputAmount: "3076923",
OutputAmount: "40000000",
}, nil
}

View File

@ -20,3 +20,13 @@ type MaxLimitResult struct {
type TokenInPoolResult struct {
CanSwapFrom bool `json:"canSwapFrom"`
}
type CreditSendLimitsResult struct {
MaxRAT string `json:"maxRAT"`
MaxSAT string `json:"maxSAT"`
}
type CreditSendReverseQouteResult struct {
InputAmount string `json:"inputAmount"`
OutputAmount string `json:"outputAmount"`
}

View File

@ -30,4 +30,6 @@ type AccountService interface {
PoolSwap(ctx context.Context, amount, from, fromTokenAddress, poolAddress, toTokenAddress string) (*models.PoolSwapResult, error)
GetSwapFromTokenMaxLimit(ctx context.Context, poolAddress, fromTokenAddress, toTokenAddress, publicKey string) (*models.MaxLimitResult, error)
CheckTokenInPool(ctx context.Context, poolAddress, tokenAddress string) (*models.TokenInPoolResult, error)
GetCreditSendMaxLimit(ctx context.Context, poolAddress, fromTokenAddress, toTokenAddress, publicKey string) (*models.CreditSendLimitsResult, error)
GetCreditSendReverseQuote(ctx context.Context, poolAddress, fromTokenAddress, toTokenAddress, toTokenAMount string) (*models.CreditSendReverseQouteResult, error)
}

View File

@ -27,11 +27,36 @@ var (
logg = slogging.Get().With("component", "sarafu-api.devapi")
)
type APIError struct {
Code string
Description string
}
func (e *APIError) Error() string {
if e.Code != "" {
return fmt.Sprintf("[%s] %s", e.Code, e.Description)
}
return e.Description
}
type HTTPAccountService struct {
SS storage.StorageService
UseApi bool
}
// symbolReplacements holds mappings of invalid symbols → valid ones
var symbolReplacements = map[string]string{
"USD₮": "USDT",
}
// sanitizeSymbol replaces known invalid token symbols with normalized ones
func sanitizeSymbol(symbol string) string {
if replacement, ok := symbolReplacements[symbol]; ok {
return replacement
}
return symbol
}
// Parameters:
// - trackingId: A unique identifier for the account.This should be obtained from a previous call to
// CreateAccount or a similar function that returns an AccountResponse. The `trackingId` field in the
@ -130,6 +155,11 @@ func (as *HTTPAccountService) FetchVouchers(ctx context.Context, publicKey strin
return nil, err
}
// Normalize symbols before returning
for i := range r.Holdings {
r.Holdings[i].TokenSymbol = sanitizeSymbol(r.Holdings[i].TokenSymbol)
}
return r.Holdings, nil
}
@ -156,6 +186,11 @@ func (as *HTTPAccountService) FetchTransactions(ctx context.Context, publicKey s
return nil, err
}
// Normalize symbols before returning
for i := range r.Transfers {
r.Transfers[i].TokenSymbol = sanitizeSymbol(r.Transfers[i].TokenSymbol)
}
return r.Transfers, nil
}
@ -177,6 +212,9 @@ func (as *HTTPAccountService) VoucherData(ctx context.Context, address string) (
return nil, err
}
// Normalize symbols before returning
r.TokenDetails.TokenSymbol = sanitizeSymbol(r.TokenDetails.TokenSymbol)
_, err = doRequest(ctx, req, &r)
return &r.TokenDetails, err
}
@ -367,7 +405,6 @@ func (as *HTTPAccountService) GetPoolSwappableFromVouchers(ctx context.Context,
svc := dev.NewDevAccountService(ctx, as.SS)
return svc.GetPoolSwappableFromVouchers(ctx, poolAddress, publicKey)
}
}
func (as *HTTPAccountService) getPoolSwappableFromVouchers(ctx context.Context, poolAddress, publicKey string) ([]dataserviceapi.TokenHoldings, error) {
@ -383,6 +420,14 @@ func (as *HTTPAccountService) getPoolSwappableFromVouchers(ctx context.Context,
return nil, err
}
_, err = doRequest(ctx, req, &r)
if err != nil {
return nil, err
}
// Normalize symbols before returning
for i := range r.PoolSwappableVouchers {
r.PoolSwappableVouchers[i].TokenSymbol = sanitizeSymbol(r.PoolSwappableVouchers[i].TokenSymbol)
}
return r.PoolSwappableVouchers, nil
}
@ -423,6 +468,15 @@ func (as HTTPAccountService) getPoolSwappableVouchers(ctx context.Context, poolA
}
_, err = doRequest(ctx, req, &r)
if err != nil {
return nil, err
}
// Normalize symbols before returning
for i := range r.PoolSwappableVouchers {
r.PoolSwappableVouchers[i].TokenSymbol = sanitizeSymbol(r.PoolSwappableVouchers[i].TokenSymbol)
}
return r.PoolSwappableVouchers, nil
}
@ -463,10 +517,10 @@ func (as *HTTPAccountService) GetSwapFromTokenMaxLimit(ctx context.Context, pool
}
}
func (as *HTTPAccountService) getSwapFromTokenMaxLimit(ctx context.Context, poolAddress, fromTokenAddress, toTokeAddress, publicKey string) (*models.MaxLimitResult, error) {
func (as *HTTPAccountService) getSwapFromTokenMaxLimit(ctx context.Context, poolAddress, fromTokenAddress, toTokenAddress, publicKey string) (*models.MaxLimitResult, error) {
var r models.MaxLimitResult
ep, err := url.JoinPath(config.PoolSwappableVouchersURL, poolAddress, "limit", fromTokenAddress, toTokeAddress, publicKey)
ep, err := url.JoinPath(config.PoolSwappableVouchersURL, poolAddress, "limit", fromTokenAddress, toTokenAddress, publicKey)
if err != nil {
return nil, err
}
@ -688,6 +742,46 @@ func (as *HTTPAccountService) SendPINResetSMS(ctx context.Context, admin, phone
return nil
}
// GetCreditSendMaxLimit calls the API to check credit limits and return the maxRAT and maxSAT
func (as *HTTPAccountService) GetCreditSendMaxLimit(ctx context.Context, poolAddress, fromTokenAddress, toTokenAddress, publicKey string) (*models.CreditSendLimitsResult, error) {
var r models.CreditSendLimitsResult
ep, err := url.JoinPath(config.CreditSendURL, poolAddress, fromTokenAddress, toTokenAddress, publicKey)
if err != nil {
return nil, err
}
req, err := http.NewRequest("GET", ep, nil)
if err != nil {
return nil, err
}
_, err = doRequest(ctx, req, &r)
if err != nil {
return nil, err
}
return &r, nil
}
// GetCreditSendReverseQuote calls the API to getthe reverse quote for sending RAT amount
func (as *HTTPAccountService) GetCreditSendReverseQuote(ctx context.Context, poolAddress, fromTokenAddress, toTokenAddress, toTokenAMount string) (*models.CreditSendReverseQouteResult, error) {
var r models.CreditSendReverseQouteResult
ep, err := url.JoinPath(config.CreditSendReverseQuoteURL, poolAddress, fromTokenAddress, toTokenAddress, toTokenAMount)
if err != nil {
return nil, err
}
req, err := http.NewRequest("GET", ep, nil)
if err != nil {
return nil, err
}
_, err = doRequest(ctx, req, &r)
if err != nil {
return nil, err
}
return &r, nil
}
// TODO: remove eth-custodial api dependency
func doRequest(ctx context.Context, req *http.Request, rcpt any) (*api.OKResponse, error) {
var okResponse api.OKResponse
@ -720,7 +814,11 @@ func doRequest(ctx context.Context, req *http.Request, rcpt any) (*api.OKRespons
if err := json.Unmarshal(body, &errResponse); err != nil {
return nil, err
}
return nil, errors.New(errResponse.Description)
return nil, &APIError{
Code: errResponse.ErrCode,
Description: errResponse.Description,
}
}
if err := json.Unmarshal(body, &okResponse); err != nil {
@ -728,7 +826,7 @@ func doRequest(ctx context.Context, req *http.Request, rcpt any) (*api.OKRespons
}
if len(okResponse.Result) == 0 {
return nil, errors.New("Empty api result")
return nil, errors.New("empty api result")
}
v, err := json.Marshal(okResponse.Result)

View File

@ -120,3 +120,13 @@ func (m MockAccountService) CheckTokenInPool(ctx context.Context, poolAddress, t
args := m.Called(poolAddress, tokenAddress)
return args.Get(0).(*models.TokenInPoolResult), args.Error(1)
}
func (m MockAccountService) GetCreditSendMaxLimit(ctx context.Context, poolAddress, fromTokenAddress, toTokenAddress, publicKey string) (*models.CreditSendLimitsResult, error) {
args := m.Called(poolAddress, fromTokenAddress, toTokenAddress, publicKey)
return args.Get(0).(*models.CreditSendLimitsResult), args.Error(1)
}
func (m MockAccountService) GetCreditSendReverseQuote(ctx context.Context, poolAddress, fromTokenAddress, toTokenAddress, toTokenAMount string) (*models.CreditSendReverseQouteResult, error) {
args := m.Called(poolAddress, fromTokenAddress, toTokenAddress, toTokenAMount)
return args.Get(0).(*models.CreditSendReverseQouteResult), args.Error(1)
}

View File

@ -8,6 +8,8 @@ import (
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
)
// This is used in the menu traversal tests
type TestAccountService struct {
}
@ -34,12 +36,18 @@ func (tas *TestAccountService) TrackAccountStatus(ctx context.Context, publicKey
func (tas *TestAccountService) FetchVouchers(ctx context.Context, publicKey string) ([]dataserviceapi.TokenHoldings, error) {
return []dataserviceapi.TokenHoldings{
dataserviceapi.TokenHoldings{
{
TokenAddress: "0x6CC75A06ac72eB4Db2eE22F781F5D100d8ec03ee",
TokenSymbol: "SRF",
TokenDecimals: "6",
Balance: "2745987",
},
{
TokenAddress: "0x3f195a3F68BF4c6D49748eFa033a00C6634fF311",
TokenSymbol: "USD",
TokenDecimals: "6",
Balance: "4269100",
},
}, nil
}
@ -116,3 +124,11 @@ func (m TestAccountService) GetSwapFromTokenMaxLimit(ctx context.Context, poolAd
func (m TestAccountService) CheckTokenInPool(ctx context.Context, poolAddress, tokenAddress string) (*models.TokenInPoolResult, error) {
return &models.TokenInPoolResult{}, nil
}
func (m TestAccountService) GetCreditSendMaxLimit(ctx context.Context, poolAddress, fromTokenAddress, toTokenAddress, publicKey string) (*models.CreditSendLimitsResult, error) {
return &models.CreditSendLimitsResult{}, nil
}
func (m TestAccountService) GetCreditSendReverseQuote(ctx context.Context, poolAddress, fromTokenAddress, toTokenAddress, toTokenAMount string) (*models.CreditSendReverseQouteResult, error) {
return &models.CreditSendReverseQouteResult{}, nil
}