Compare commits

..

80 Commits

Author SHA1 Message Date
04f33d0b6c Merge pull request 'added the grassrootseconomics/go-vise package to sarafu-api' (#16) from update-go-vise into master
Reviewed-on: #16
2025-11-19 11:22:28 +01:00
74e5fb016c Merge branch 'master' into update-go-vise 2025-11-18 13:11:54 +03:00
0f3e084a53 Merge pull request 'credit-send-endpoints' (#19) from credit-send-endpoints into master
Reviewed-on: #19
2025-11-18 11:11:00 +01:00
fe897cca84
added the GetCreditSendReverseQuote to tests 2025-10-28 11:34:21 +03:00
7eaa771eb4
added the GetCreditSendReverseQuote function 2025-10-28 11:33:17 +03:00
72af514cf3
added the CreditSendReverseQouteResult type for API responses 2025-10-28 11:32:51 +03:00
81ff6c4034
added the CreditSendReverseQuote prefix and URL 2025-10-28 11:32:03 +03:00
a705443786
added a comment to describe the use of the TestAccountService 2025-10-28 11:10:48 +03:00
af76541c86
added the USD voucher to the TokenHoldings 2025-10-28 11:09:33 +03:00
75a7ec6b32
correct the spelling of toTokenAddress 2025-10-28 11:06:01 +03:00
3e82e16923
added the GetCreditSendMaxLimit to tests 2025-10-28 11:04:43 +03:00
c11060648d
added the CreditSendLimitsResult type for the API responses 2025-10-28 11:04:12 +03:00
01569b9b39
added the GetCreditSendMaxLimit function 2025-10-28 11:03:08 +03:00
96c323f202
added the CreditSendPrefix and URL 2025-10-28 11:02:33 +03:00
2731a787e3 Merge pull request 'handle-error-codes' (#18) from handle-error-codes into master
Reviewed-on: #18 and is working on prod
2025-10-27 11:29:55 +01:00
532547899f
include the error code 2025-10-22 11:46:13 +03:00
6f7802b58c
modify doRequest() to return APIError on err 2025-10-21 15:05:22 +03:00
73e6220a8c
create a custom error struct that carries both fields from the API 2025-10-21 15:02:36 +03:00
8d4fbb9c2e Merge pull request 'Normalize symbols before returning' (#17) from sanitize-symbols into master
Reviewed-on: #17

Merged after successful tests
2025-10-06 11:02:42 +02:00
61410b2b29
Normalize symbols before returning 2025-10-06 11:43:25 +03:00
3c8e3d1bc8
added the grassrootseconomics/go-vise package to sarafu-api 2025-08-19 12:43:50 +03:00
c44ac0116f Merge pull request 'update-alias' (#15) from update-alias into master
Reviewed-on: #15
2025-08-19 11:35:23 +02:00
814bef2b20
fix: have the same order of received variables 2025-07-01 00:49:12 +03:00
12940bb5f2
fix: use the correct PUT request method 2025-07-01 00:36:06 +03:00
50ee455e70
fix: use the correct AliasUpdateURL 2025-07-01 00:31:35 +03:00
57ee409f96
add UpdateAlias to the DevAccountService and mocks 2025-06-26 09:54:19 +03:00
49a8184d02
add the UpdateAlias function 2025-06-26 09:53:26 +03:00
9a6ab7e6e2
add the endpoint for updating the alias 2025-06-26 09:52:31 +03:00
d177942bd2 Merge pull request 'pool-swap-endpoints' (#11) from pool-swap-endpoints into master
Reviewed-on: #11
2025-06-26 07:58:53 +02:00
339ba854c9
fix: correctly unmarshal nested pool details response in retrievePoolDeta 2025-06-24 12:07:44 +03:00
5aa032400c
update the alias endpoints 2025-06-24 10:48:30 +03:00
7b42d509e6
log the request and response body 2025-06-23 10:50:57 +03:00
d945964b0b
use the TokenAddress instead of ContractAddress 2025-06-23 10:00:26 +03:00
c1797e7a32
use the TokenHoldings instead of TokenDetails 2025-06-23 09:32:34 +03:00
54dfe037b4
updated the ussd-data-service to tag v1.5.0-beta 2025-06-23 09:31:53 +03:00
b5ccaea575
add the RetrievePoolDetails to the testutil and dev account service 2025-06-06 22:42:35 +03:00
bf830e92de
add the RetrievePoolDetails to the account_service 2025-06-05 20:41:08 +03:00
24224e553d
use the correct PoolSwapURL when performing the actual swap 2025-05-22 15:31:08 +03:00
6c3719e3b6
Fix: use the correct TokenDetails in the GetPoolSwappableVouchers 2025-05-21 17:12:46 +03:00
62cc5eed89
use the updated ussd-data-service 2025-05-21 17:10:25 +03:00
343d30a2f2
rename the variables and read the stables param from the config 2025-05-21 15:42:06 +03:00
a354371a94
add the IncludeStablesParam config 2025-05-21 15:40:56 +03:00
857f564aae
Fix: remove the unused publicKey from the GetPoolSwappableVouchers 2025-05-21 15:22:31 +03:00
f234d51104
Fix: update the getPoolSwappableVouchers to pass a 'stable' query parameter 2025-05-21 15:21:27 +03:00
81fbc2574c
Fix: use the correct getPoolSwappableVouchers 2025-05-20 15:50:35 +03:00
334aa07f9f
Fix: Correct JSON mapping for checkTokenInPool and getSwapFromTokenMaxLimit API responses 2025-05-20 15:48:13 +03:00
e6677015d9
use the latest ussd-data-service 2025-05-19 10:36:47 +03:00
050998ff82
added CheckTokenInPool to the account_service and test utils 2025-05-17 14:45:12 +03:00
ee434dba69
added CheckTokenInPool to check whether a token can be swapped from in a pool 2025-05-17 14:37:06 +03:00
f101ffd4c9
added a model for TokenInPoolResult 2025-05-17 14:36:26 +03:00
8b2bd72143
format document 2025-05-17 14:35:53 +03:00
3b85167ad8
Merge branch 'master' into pool-swap-endpoints 2025-05-16 12:43:26 +03:00
5d221b8d56 Merge pull request 'sms-address-pin-reset' (#14) from sms-address-pin-reset into master
Reviewed-on: #14
2025-04-28 10:27:11 +02:00
341901ec83
match new external sms endpoint 2025-04-28 11:26:13 +03:00
ed416c57c7
chore: rename EXTRA_SMS_BASE to EXTERNAL_SMS_BASE 2025-04-28 11:15:11 +03:00
6fc99661f7
chore: remove extra underscore in EXTRA_SMS_BASE env identifier 2025-04-28 11:08:39 +03:00
d509477de3
remove accidentallypushed bearer token 2025-04-28 11:03:34 +03:00
4ea8fd1f04
chore: rename extrasms prefixes to external sms 2025-04-28 10:56:05 +03:00
5c0ed48e67
add required sms methods 2025-04-17 23:14:42 +03:00
5ff139b649
add expected accountservice methods 2025-04-17 23:07:33 +03:00
86cfb9c020
match expected method signature 2025-04-17 22:18:30 +03:00
58366274c3
add required AccountService methods 2025-04-17 18:58:12 +03:00
b85511e436
implement SendPINResetSMS and SendAddressSMS 2025-04-17 18:57:24 +03:00
a243fe8c29
add SendAddressSMS and SendPINResetSMS 2025-04-17 18:57:02 +03:00
492b101aa4
setup extra sms api endpoints 2025-04-17 18:56:20 +03:00
34957e5b6f Merge pull request 'sms-upsell-api' (#10) from sms-api into master
Reviewed-on: #10
2025-04-11 10:06:08 +02:00
6597d6b6a2
added the SendUpsellSMS to DevAccountService and MockApi 2025-04-11 11:02:23 +03:00
441e289854
implement SendUpsellSMs 2025-04-01 15:25:10 +03:00
5b41c8dc64
chore: rename SendSMS to SendUpsellSMS 2025-04-01 14:55:03 +03:00
417e7f0179
fix: correct sendSms response 2025-04-01 14:53:30 +03:00
2eed990921
Merge branch 'master' into sms-api 2025-04-01 14:18:04 +03:00
56ed9b2439
Merge branch 'multi-pool' into pool-swap-endpoints 2025-03-20 08:35:22 +03:00
064e749b04
Merge branch 'master' into pool-swap-endpoints 2025-03-19 15:22:30 +03:00
80c2493e80
implement get token pool limit,get all pool swappable vouchers 2025-03-19 12:09:52 +03:00
4444ac0255
add pool swap endpoints 2025-03-19 12:07:11 +03:00
720fa94b56
register pool endpoints 2025-03-17 20:40:56 +03:00
ee04552cbb
feat: implement fetch top pools,retrieve pool details using a pool symbol 2025-03-17 20:40:19 +03:00
4a5de68a8c
added the SendSMS func to the httpAccount service 2025-03-06 09:06:45 +03:00
6c4702e2ba
added the send sms response model 2025-03-06 09:05:39 +03:00
7280784ee1
added the send sms endpoint configs 2025-03-06 09:05:01 +03:00
11 changed files with 710 additions and 146 deletions

View File

@ -15,11 +15,19 @@ const (
voucherHoldingsPathPrefix = "/api/v1/holdings" voucherHoldingsPathPrefix = "/api/v1/holdings"
voucherTransfersPathPrefix = "/api/v1/transfers/last10" voucherTransfersPathPrefix = "/api/v1/transfers/last10"
voucherDataPathPrefix = "/api/v1/token" voucherDataPathPrefix = "/api/v1/token"
aliasPrefix = "api/v1/alias" SendSMSPrefix = "api/v1/external/upsell"
poolDepositPrefix = "/api/v2/pool/deposit" poolDepositPrefix = "/api/v2/pool/deposit"
poolSwapQoutePrefix = "/api/v2/pool/quote" poolSwapQoutePrefix = "/api/v2/pool/quote"
poolSwapPrefix = "/api/v2/pool/swap" poolSwapPrefix = "/api/v2/pool/swap"
AliasEnsPrefix = "/api/v1/bypass" 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 ( var (
@ -27,6 +35,8 @@ var (
dataURLBase string dataURLBase string
BearerToken string BearerToken string
aliasEnsURLBase string aliasEnsURLBase string
externalSMSBase string
IncludeStablesParam string
) )
var ( var (
@ -38,11 +48,19 @@ var (
VoucherHoldingsURL string VoucherHoldingsURL string
VoucherTransfersURL string VoucherTransfersURL string
VoucherDataURL string VoucherDataURL string
CheckAliasURL string
PoolDepositURL string PoolDepositURL string
PoolSwapQuoteURL string PoolSwapQuoteURL string
PoolSwapURL string PoolSwapURL string
AliasEnsURL string TopPoolsURL string
RetrievePoolDetailsURL string
PoolSwappableVouchersURL string
SendSMSURL string
AliasRegistrationURL string
AliasResolverURL string
ExternalSMSURL string
AliasUpdateURL string
CreditSendURL string
CreditSendReverseQuoteURL string
) )
func setBase() error { func setBase() error {
@ -51,7 +69,9 @@ func setBase() error {
custodialURLBase = env.GetEnv("CUSTODIAL_URL_BASE", "http://localhost:5003") custodialURLBase = env.GetEnv("CUSTODIAL_URL_BASE", "http://localhost:5003")
dataURLBase = env.GetEnv("DATA_URL_BASE", "http://localhost:5006") dataURLBase = env.GetEnv("DATA_URL_BASE", "http://localhost:5006")
aliasEnsURLBase = env.GetEnv("ALIAS_ENS_BASE", "http://localhost:5015") aliasEnsURLBase = env.GetEnv("ALIAS_ENS_BASE", "http://localhost:5015")
externalSMSBase = env.GetEnv("EXTERNAL_SMS_BASE", "http://localhost:5035")
BearerToken = env.GetEnv("BEARER_TOKEN", "") BearerToken = env.GetEnv("BEARER_TOKEN", "")
IncludeStablesParam = env.GetEnv("INCLUDE_STABLES_PARAM", "false")
_, err = url.Parse(custodialURLBase) _, err = url.Parse(custodialURLBase)
if err != nil { if err != nil {
@ -78,11 +98,19 @@ func LoadConfig() error {
VoucherHoldingsURL, _ = url.JoinPath(dataURLBase, voucherHoldingsPathPrefix) VoucherHoldingsURL, _ = url.JoinPath(dataURLBase, voucherHoldingsPathPrefix)
VoucherTransfersURL, _ = url.JoinPath(dataURLBase, voucherTransfersPathPrefix) VoucherTransfersURL, _ = url.JoinPath(dataURLBase, voucherTransfersPathPrefix)
VoucherDataURL, _ = url.JoinPath(dataURLBase, voucherDataPathPrefix) VoucherDataURL, _ = url.JoinPath(dataURLBase, voucherDataPathPrefix)
CheckAliasURL, _ = url.JoinPath(dataURLBase, aliasPrefix) SendSMSURL, _ = url.JoinPath(dataURLBase, SendSMSPrefix)
PoolDepositURL, _ = url.JoinPath(custodialURLBase, poolDepositPrefix) PoolDepositURL, _ = url.JoinPath(custodialURLBase, poolDepositPrefix)
PoolSwapQuoteURL, _ = url.JoinPath(custodialURLBase, poolSwapQoutePrefix) PoolSwapQuoteURL, _ = url.JoinPath(custodialURLBase, poolSwapQoutePrefix)
PoolSwapURL, _ = url.JoinPath(custodialURLBase, poolSwapPrefix) PoolSwapURL, _ = url.JoinPath(custodialURLBase, poolSwapPrefix)
TopPoolsURL, _ = url.JoinPath(dataURLBase, topPoolsPrefix)
RetrievePoolDetailsURL, _ = url.JoinPath(dataURLBase, retrievePoolDetailsPrefix)
PoolSwappableVouchersURL, _ = url.JoinPath(dataURLBase, poolSwappableVouchersPrefix)
AliasRegistrationURL, _ = url.JoinPath(aliasEnsURLBase, AliasRegistrationPrefix)
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)
AliasEnsURL, _ = url.JoinPath(aliasEnsURLBase, AliasEnsPrefix)
return nil return nil
} }

View File

@ -12,18 +12,18 @@ import (
"strings" "strings"
"time" "time"
"git.defalsify.org/vise.git/db"
"git.defalsify.org/vise.git/logging"
"git.grassecon.net/grassrootseconomics/common/phone" "git.grassecon.net/grassrootseconomics/common/phone"
"git.grassecon.net/grassrootseconomics/sarafu-api/event" "git.grassecon.net/grassrootseconomics/sarafu-api/event"
"git.grassecon.net/grassrootseconomics/sarafu-api/models" "git.grassecon.net/grassrootseconomics/sarafu-api/models"
"git.grassecon.net/grassrootseconomics/visedriver/storage" "git.grassecon.net/grassrootseconomics/visedriver/storage"
"github.com/gofrs/uuid" "github.com/gofrs/uuid"
"github.com/grassrootseconomics/go-vise/db"
slogging "github.com/grassrootseconomics/go-vise/slog"
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
) )
var ( var (
logg = logging.NewVanilla().WithDomain("sarafu-api.devapi") logg = slogging.Get().With("component", "sarafu-api.devapi")
aliasRegex = regexp.MustCompile("^\\+?[a-zA-Z0-9\\-_]+$") aliasRegex = regexp.MustCompile("^\\+?[a-zA-Z0-9\\-_]+$")
searchDomain = ".sarafu.local" searchDomain = ".sarafu.local"
) )
@ -233,7 +233,6 @@ func (das *DevAccountService) loadItem(ctx context.Context, k []byte, v []byte)
if len(ss) != 2 { if len(ss) != 2 {
return fmt.Errorf("malformed key: %s", s) return fmt.Errorf("malformed key: %s", s)
} }
fmt.Println("S1", ss[0])
if ss[0] == "account" { if ss[0] == "account" {
err = das.loadAccount(ctx, ss[1], v) err = das.loadAccount(ctx, ss[1], v)
logg.ErrorCtxf(ctx, "loading saved account failed", "error_load_account", err) logg.ErrorCtxf(ctx, "loading saved account failed", "error_load_account", err)
@ -588,7 +587,7 @@ func (das *DevAccountService) FetchVouchers(ctx context.Context, publicKey strin
//TODO: Iterate over the account acc.Balances object //TODO: Iterate over the account acc.Balances object
for _, voucher := range das.vouchers { for _, voucher := range das.vouchers {
holdings = append(holdings, dataserviceapi.TokenHoldings{ holdings = append(holdings, dataserviceapi.TokenHoldings{
ContractAddress: voucher.Address, TokenAddress: voucher.Address,
TokenSymbol: voucher.Symbol, TokenSymbol: voucher.Symbol,
TokenDecimals: strconv.Itoa(voucher.Decimals), TokenDecimals: strconv.Itoa(voucher.Decimals),
Balance: strconv.Itoa(int(defaultVoucherBalance)), Balance: strconv.Itoa(int(defaultVoucherBalance)),
@ -807,6 +806,28 @@ func (das *DevAccountService) RequestAlias(ctx context.Context, publicKey string
}, nil }, nil
} }
func (das *DevAccountService) UpdateAlias(ctx context.Context, publicKey string, name string) (*models.RequestAliasResult, error) {
logg.DebugCtxf(ctx, "Updated the alias", "address", publicKey, "name", name)
return &models.RequestAliasResult{
Alias: name,
}, nil
}
func (das *DevAccountService) SendUpsellSMS(ctx context.Context, inviterPhone, inviteePhone string) (*models.SendSMSResponse, error) {
logg.DebugCtxf(ctx, "sent an SMS", "inviterPhone", inviterPhone, "inviteePhone", inviteePhone)
return &models.SendSMSResponse{
Invitee: inviteePhone,
}, nil
}
func (das *DevAccountService) SendPINResetSMS(ctx context.Context, admin, phone string) error {
return fmt.Errorf("unimplemented")
}
func (das *DevAccountService) SendAddressSMS(ctx context.Context, publicKey, originPhone string) error {
return fmt.Errorf("unimplemented")
}
func (das *DevAccountService) FetchTopPools(ctx context.Context) ([]dataserviceapi.PoolDetails, error) { func (das *DevAccountService) FetchTopPools(ctx context.Context) ([]dataserviceapi.PoolDetails, error) {
var topPools []dataserviceapi.PoolDetails var topPools []dataserviceapi.PoolDetails
for _, p := range das.pools { for _, p := range das.pools {
@ -819,6 +840,18 @@ func (das *DevAccountService) FetchTopPools(ctx context.Context) ([]dataservicea
return topPools, nil return topPools, nil
} }
func (das *DevAccountService) RetrievePoolDetails(ctx context.Context, sym string) (*dataserviceapi.PoolDetails, error) {
testPool := &dataserviceapi.PoolDetails{
PoolName: "DevTest",
PoolSymbol: "DEVT",
PoolContractAdrress: "0x145F87d6198dEDD45C614FFD8b70E9a2fCCc5cc9",
LimiterAddress: "",
VoucherRegistry: "",
}
return testPool, nil
}
func (das *DevAccountService) GetPoolSwappableFromVouchers(ctx context.Context, poolAddress, publicKey string) ([]dataserviceapi.TokenHoldings, error) { func (das *DevAccountService) GetPoolSwappableFromVouchers(ctx context.Context, poolAddress, publicKey string) ([]dataserviceapi.TokenHoldings, error) {
var swapFromList []dataserviceapi.TokenHoldings var swapFromList []dataserviceapi.TokenHoldings
@ -828,7 +861,7 @@ func (das *DevAccountService) GetPoolSwappableFromVouchers(ctx context.Context,
} }
for _, v := range p.Vouchers { for _, v := range p.Vouchers {
swapFromList = append(swapFromList, dataserviceapi.TokenHoldings{ swapFromList = append(swapFromList, dataserviceapi.TokenHoldings{
ContractAddress: v.Address, TokenAddress: v.Address,
TokenSymbol: v.Symbol, TokenSymbol: v.Symbol,
TokenDecimals: string(defaultDecimals), TokenDecimals: string(defaultDecimals),
Balance: fmt.Sprintf("%f", defaultVoucherBalance), Balance: fmt.Sprintf("%f", defaultVoucherBalance),
@ -838,7 +871,7 @@ func (das *DevAccountService) GetPoolSwappableFromVouchers(ctx context.Context,
return swapFromList, nil return swapFromList, nil
} }
func (das *DevAccountService) GetPoolSwappableVouchers(ctx context.Context, poolAddress, publicKey string) ([]dataserviceapi.TokenHoldings, error) { func (das *DevAccountService) GetPoolSwappableVouchers(ctx context.Context, poolAddress string) ([]dataserviceapi.TokenHoldings, error) {
var swapToList []dataserviceapi.TokenHoldings var swapToList []dataserviceapi.TokenHoldings
_, ok := das.pools[poolAddress] _, ok := das.pools[poolAddress]
if !ok { if !ok {
@ -846,17 +879,15 @@ func (das *DevAccountService) GetPoolSwappableVouchers(ctx context.Context, pool
} }
for _, voucher := range das.vouchers { for _, voucher := range das.vouchers {
swapToList = append(swapToList, dataserviceapi.TokenHoldings{ swapToList = append(swapToList, dataserviceapi.TokenHoldings{
ContractAddress: voucher.Address, TokenAddress: voucher.Address,
TokenSymbol: voucher.Symbol, TokenSymbol: voucher.Symbol,
TokenDecimals: string(defaultDecimals), TokenDecimals: strconv.Itoa(voucher.Decimals),
Balance: fmt.Sprintf("%f", defaultVoucherBalance),
}) })
} }
return swapToList, nil return swapToList, nil
} }
func (das *DevAccountService) GetSwapFromTokenMaxLimit(ctx context.Context, poolAddress, fromTokenAddress, toTokenAddress, publicKey string) (*models.MaxLimitResult, error) { func (das *DevAccountService) GetSwapFromTokenMaxLimit(ctx context.Context, poolAddress, fromTokenAddress, toTokenAddress, publicKey string) (*models.MaxLimitResult, error) {
p, ok := das.pools[poolAddress] p, ok := das.pools[poolAddress]
if !ok { if !ok {
return nil, fmt.Errorf("Pool address: %v not found ", poolAddress) return nil, fmt.Errorf("Pool address: %v not found ", poolAddress)
@ -870,3 +901,23 @@ func (das *DevAccountService) GetSwapFromTokenMaxLimit(ctx context.Context, pool
Max: limit, Max: limit,
}, nil }, nil
} }
func (das *DevAccountService) CheckTokenInPool(ctx context.Context, poolAddress, tokenAddress string) (*models.TokenInPoolResult, error) {
return &models.TokenInPoolResult{
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
}

24
go.mod
View File

@ -1,36 +1,36 @@
module git.grassecon.net/grassrootseconomics/sarafu-api module git.grassecon.net/grassrootseconomics/sarafu-api
go 1.23.4 go 1.24
toolchain go1.24.6
require ( require (
git.defalsify.org/vise.git v0.2.3-0.20250120121301-10739fb4a8c9
git.grassecon.net/grassrootseconomics/common v0.0.0-20250121134736-ba8cbbccea7d git.grassecon.net/grassrootseconomics/common v0.0.0-20250121134736-ba8cbbccea7d
git.grassecon.net/grassrootseconomics/visedriver v0.8.0-beta.10.0.20250122123424-6749c632b0a2 git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250819084006-5a9c82207578
github.com/gofrs/uuid v4.4.0+incompatible github.com/gofrs/uuid v4.4.0+incompatible
github.com/grassrootseconomics/eth-custodial v1.3.0-beta github.com/grassrootseconomics/eth-custodial v1.3.0-beta
github.com/grassrootseconomics/ussd-data-service v1.4.0-beta github.com/grassrootseconomics/go-vise v0.5.0
github.com/grassrootseconomics/ussd-data-service v1.6.0-beta
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
) )
require ( require (
github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c // indirect github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/fxamacker/cbor/v2 v2.4.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 // indirect
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
github.com/jackc/pgx/v5 v5.7.1 // indirect github.com/jackc/pgx/v5 v5.7.5 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/joho/godotenv v1.5.1 // indirect github.com/joho/godotenv v1.5.1 // indirect
github.com/kr/text v0.2.0 // indirect github.com/kr/text v0.2.0 // indirect
github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a // indirect github.com/leonelquinteros/gotext v1.7.2 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/objx v0.5.2 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
golang.org/x/crypto v0.32.0 // indirect golang.org/x/crypto v0.40.0 // indirect
golang.org/x/sync v0.10.0 // indirect golang.org/x/sync v0.16.0 // indirect
golang.org/x/text v0.21.0 // indirect golang.org/x/text v0.27.0 // indirect
gopkg.in/leonelquinteros/gotext.v1 v1.3.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

48
go.sum
View File

@ -1,33 +1,29 @@
git.defalsify.org/vise.git v0.2.3-0.20250120121301-10739fb4a8c9 h1:sPcqXQcywxA8W3W+9qQncLPmsrgqTIlec7vmD4/7vyA=
git.defalsify.org/vise.git v0.2.3-0.20250120121301-10739fb4a8c9/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck=
git.grassecon.net/grassrootseconomics/common v0.0.0-20250121134736-ba8cbbccea7d h1:5mzLas+jxTUtusOKx4XvU+n2QvrV/mH17MnJRy46siQ= git.grassecon.net/grassrootseconomics/common v0.0.0-20250121134736-ba8cbbccea7d h1:5mzLas+jxTUtusOKx4XvU+n2QvrV/mH17MnJRy46siQ=
git.grassecon.net/grassrootseconomics/common v0.0.0-20250121134736-ba8cbbccea7d/go.mod h1:wgQJZGIS6QuNLHqDhcsvehsbn5PvgV7aziRebMnJi60= git.grassecon.net/grassrootseconomics/common v0.0.0-20250121134736-ba8cbbccea7d/go.mod h1:wgQJZGIS6QuNLHqDhcsvehsbn5PvgV7aziRebMnJi60=
git.grassecon.net/grassrootseconomics/visedriver v0.8.0-beta.10.0.20250122123424-6749c632b0a2 h1:ON77G5K0JNuwPb5JT/hRfF6G6+xstlBQgEIEzWydnhg= git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250819084006-5a9c82207578 h1:GKhBMVbjGBus3eAp2tw3M66irOnEWWg0QEKVn0bBS8E=
git.grassecon.net/grassrootseconomics/visedriver v0.8.0-beta.10.0.20250122123424-6749c632b0a2/go.mod h1:pjKp9L/ZsWW3kMB0UoIl1yv9TBIuU33mn9Aghxp7vGk= git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250819084006-5a9c82207578/go.mod h1:hx6mjSyxKv5oxiJxB6EevJrMJIYjVoYxFEzBtpD+29c=
github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c h1:H9Nm+I7Cg/YVPpEV1RzU3Wq2pjamPc/UtHDgItcb7lE= github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c h1:H9Nm+I7Cg/YVPpEV1RzU3Wq2pjamPc/UtHDgItcb7lE=
github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c/go.mod h1:rGod7o6KPeJ+hyBpHfhi4v7blx9sf+QsHsA7KAsdN6U= github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c/go.mod h1:rGod7o6KPeJ+hyBpHfhi4v7blx9sf+QsHsA7KAsdN6U=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
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 v1.2.0-beta h1:fn1gwbWIwHVEBtUC2zi5OqTlfI/5gU1SMk0fgGixIXk= github.com/grassrootseconomics/go-vise v0.5.0 h1:FRg2de55Eb5SisrgTBeFWfWX+sXwp5q9r7YWtKWDwsk=
github.com/grassrootseconomics/ussd-data-service v1.2.0-beta/go.mod h1:omfI0QtUwIdpu9gMcUqLMCG8O1XWjqJGBx1qUMiGWC0= github.com/grassrootseconomics/go-vise v0.5.0/go.mod h1:b2/q4jfTu2i1wyUwYUu7FYq4m2f1AZv8MpiDM3ZcoGo=
github.com/grassrootseconomics/ussd-data-service v1.4.0-beta h1:4fMd/3h2ZIhRg4GdHQmRw5FfD3MpJvFNNJQo+Q27f5M= github.com/grassrootseconomics/ussd-data-service v1.6.0-beta h1:pY6zns6ifXyClRxP+JJaWrged3oRE7tTS2xaftF9clA=
github.com/grassrootseconomics/ussd-data-service v1.4.0-beta/go.mod h1:9sGnorpKaK76FmOGXoh/xv7x5siSFNYdXxQo9BKW4DI= github.com/grassrootseconomics/ussd-data-service v1.6.0-beta/go.mod h1:9sGnorpKaK76FmOGXoh/xv7x5siSFNYdXxQo9BKW4DI=
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 h1:U4kkNYryi/qfbBF8gh7Vsbuz+cVmhf5kt6pE9bYYyLo=
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4/go.mod h1:zpZDgZFzeq9s0MIeB1P50NIEWDFFHSFBohI/NbaTD/Y=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
@ -36,10 +32,12 @@ github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a h1:0Q3H0YXzMHiciXtRcM+j0jiCe8WKPQHoRgQiRTnfcLY= github.com/leonelquinteros/gotext v1.7.2 h1:bDPndU8nt+/kRo1m4l/1OXiiy2v7Z7dfPQ9+YP7G1Mc=
github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a/go.mod h1:CdTTBOYzS5E4mWS1N8NWP6AHI19MP0A2B18n3hLzRMk= github.com/leonelquinteros/gotext v1.7.2/go.mod h1:9/haCkm5P7Jay1sxKDGJ5WIg4zkz8oZKw4ekNpALob8=
github.com/pashagolub/pgxmock/v4 v4.3.0 h1:DqT7fk0OCK6H0GvqtcMsLpv8cIwWqdxWgfZNLeHCb/s= github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w=
github.com/pashagolub/pgxmock/v4 v4.3.0/go.mod h1:9VoVHXwS3XR/yPtKGzwQvwZX1kzGB9sM8SviDcHDa3A= github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
github.com/pashagolub/pgxmock/v4 v4.7.0 h1:de2ORuFYyjwOQR7NBm57+321RnZxpYiuUjsmqRiqgh8=
github.com/pashagolub/pgxmock/v4 v4.7.0/go.mod h1:9L57pC193h2aKRHVyiiE817avasIPZnPwPlw3JczWvM=
github.com/peteole/testdata-loader v0.3.0 h1:8jckE9KcyNHgyv/VPoaljvKZE0Rqr8+dPVYH6rfNr9I= github.com/peteole/testdata-loader v0.3.0 h1:8jckE9KcyNHgyv/VPoaljvKZE0Rqr8+dPVYH6rfNr9I=
github.com/peteole/testdata-loader v0.3.0/go.mod h1:Mt0ZbRtb56u8SLJpNP+BnQbENljMorYBpqlvt3cS83U= github.com/peteole/testdata-loader v0.3.0/go.mod h1:Mt0ZbRtb56u8SLJpNP+BnQbENljMorYBpqlvt3cS83U=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@ -56,17 +54,15 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/leonelquinteros/gotext.v1 v1.3.1 h1:8d9/fdTG0kn/B7NNGV1BsEyvektXFAbkMsTZS2sFSCc=
gopkg.in/leonelquinteros/gotext.v1 v1.3.1/go.mod h1:X1WlGDeAFIYsW6GjgMm4VwUwZ2XjI7Zan2InxSUQWrU=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -16,3 +16,17 @@ type PoolSwapResult struct {
type MaxLimitResult struct { type MaxLimitResult struct {
Max string `json:"max"` Max string `json:"max"`
} }
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

@ -0,0 +1,5 @@
package models
type SendSMSResponse struct {
Invitee string `json:"invitee"`
}

View File

@ -17,11 +17,19 @@ type AccountService interface {
TokenTransfer(ctx context.Context, amount, from, to, tokenAddress string) (*models.TokenTransferResponse, error) TokenTransfer(ctx context.Context, amount, from, to, tokenAddress string) (*models.TokenTransferResponse, error)
CheckAliasAddress(ctx context.Context, alias string) (*models.AliasAddress, error) CheckAliasAddress(ctx context.Context, alias string) (*models.AliasAddress, error)
RequestAlias(ctx context.Context, hint string, publicKey string) (*models.RequestAliasResult, error) RequestAlias(ctx context.Context, hint string, publicKey string) (*models.RequestAliasResult, error)
UpdateAlias(ctx context.Context, name string, publicKey string) (*models.RequestAliasResult, error)
SendUpsellSMS(ctx context.Context, inviterPhone, inviteePhone string) (*models.SendSMSResponse, error)
SendAddressSMS(ctx context.Context, publicKey, originPhone string) error
SendPINResetSMS(ctx context.Context, admin, phone string) error
PoolDeposit(ctx context.Context, amount, from, poolAddress, tokenAddress string) (*models.PoolDepositResult, error) PoolDeposit(ctx context.Context, amount, from, poolAddress, tokenAddress string) (*models.PoolDepositResult, error)
FetchTopPools(ctx context.Context) ([]dataserviceapi.PoolDetails, error) FetchTopPools(ctx context.Context) ([]dataserviceapi.PoolDetails, error)
RetrievePoolDetails(ctx context.Context, sym string) (*dataserviceapi.PoolDetails, error)
GetPoolSwappableFromVouchers(ctx context.Context, poolAddress, publicKey string) ([]dataserviceapi.TokenHoldings, error) GetPoolSwappableFromVouchers(ctx context.Context, poolAddress, publicKey string) ([]dataserviceapi.TokenHoldings, error)
GetPoolSwappableVouchers(ctx context.Context, poolAddress, publicKey string) ([]dataserviceapi.TokenHoldings, error) GetPoolSwappableVouchers(ctx context.Context, poolAddress string) ([]dataserviceapi.TokenHoldings, error)
GetPoolSwapQuote(ctx context.Context, amount, from, fromTokenAddress, poolAddress, toTokenAddress string) (*models.PoolSwapQuoteResult, error) GetPoolSwapQuote(ctx context.Context, amount, from, fromTokenAddress, poolAddress, toTokenAddress string) (*models.PoolSwapQuoteResult, error)
PoolSwap(ctx context.Context, amount, from, fromTokenAddress, poolAddress, toTokenAddress string) (*models.PoolSwapResult, error) 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) 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

@ -13,25 +13,50 @@ import (
"regexp" "regexp"
"strings" "strings"
"git.defalsify.org/vise.git/logging"
"git.grassecon.net/grassrootseconomics/sarafu-api/config" "git.grassecon.net/grassrootseconomics/sarafu-api/config"
"git.grassecon.net/grassrootseconomics/sarafu-api/dev" "git.grassecon.net/grassrootseconomics/sarafu-api/dev"
"git.grassecon.net/grassrootseconomics/sarafu-api/models" "git.grassecon.net/grassrootseconomics/sarafu-api/models"
"git.grassecon.net/grassrootseconomics/visedriver/storage" "git.grassecon.net/grassrootseconomics/visedriver/storage"
"github.com/grassrootseconomics/eth-custodial/pkg/api" "github.com/grassrootseconomics/eth-custodial/pkg/api"
slogging "github.com/grassrootseconomics/go-vise/slog"
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
) )
var ( var (
aliasRegex = regexp.MustCompile("^\\+?[a-zA-Z0-9\\-_]+$") aliasRegex = regexp.MustCompile("^\\+?[a-zA-Z0-9\\-_]+$")
logg = logging.NewVanilla().WithDomain("sarafu-api.devapi") 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 { type HTTPAccountService struct {
SS storage.StorageService SS storage.StorageService
UseApi bool 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: // Parameters:
// - trackingId: A unique identifier for the account.This should be obtained from a previous call to // - 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 // 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 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 return r.Holdings, nil
} }
@ -156,6 +186,11 @@ func (as *HTTPAccountService) FetchTransactions(ctx context.Context, publicKey s
return nil, err 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 return r.Transfers, nil
} }
@ -177,6 +212,9 @@ func (as *HTTPAccountService) VoucherData(ctx context.Context, address string) (
return nil, err return nil, err
} }
// Normalize symbols before returning
r.TokenDetails.TokenSymbol = sanitizeSymbol(r.TokenDetails.TokenSymbol)
_, err = doRequest(ctx, req, &r) _, err = doRequest(ctx, req, &r)
return &r.TokenDetails, err return &r.TokenDetails, err
} }
@ -234,39 +272,77 @@ func (as *HTTPAccountService) CheckAliasAddress(ctx context.Context, alias strin
} }
func resolveAliasAddress(ctx context.Context, alias string) (*models.AliasAddress, error) { func resolveAliasAddress(ctx context.Context, alias string) (*models.AliasAddress, error) {
var ( var aliasEnsResult models.AliasEnsAddressResult
aliasEnsResult models.AliasEnsAddressResult
)
ep, err := url.JoinPath(config.AliasEnsURL, "/resolve") fullURL, err := url.JoinPath(config.AliasResolverURL, alias)
if err != nil { if err != nil {
return nil, err return nil, err
} }
u, err := url.Parse(ep) req, err := http.NewRequest("GET", fullURL, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
query := u.Query()
query.Set("name", alias)
u.RawQuery = query.Encode()
req, err := http.NewRequest("GET", u.String(), nil)
if err != nil {
return nil, err
}
_, err = doRequest(ctx, req, &aliasEnsResult) _, err = doRequest(ctx, req, &aliasEnsResult)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &models.AliasAddress{Address: aliasEnsResult.Address}, err
return &models.AliasAddress{Address: aliasEnsResult.Address}, nil
} }
func (as *HTTPAccountService) FetchTopPools(ctx context.Context) ([]dataserviceapi.PoolDetails, error) { func (as *HTTPAccountService) FetchTopPools(ctx context.Context) ([]dataserviceapi.PoolDetails, error) {
svc := dev.NewDevAccountService(ctx, as.SS) svc := dev.NewDevAccountService(ctx, as.SS)
if as.UseApi {
return fetchCustodialTopPools(ctx)
} else {
return svc.FetchTopPools(ctx) return svc.FetchTopPools(ctx)
} }
}
func fetchCustodialTopPools(ctx context.Context) ([]dataserviceapi.PoolDetails, error) {
var r struct {
TopPools []dataserviceapi.PoolDetails `json:"topPools"`
}
req, err := http.NewRequest("GET", config.TopPoolsURL, nil)
if err != nil {
return nil, err
}
_, err = doRequest(ctx, req, &r)
return r.TopPools, nil
}
func (as *HTTPAccountService) RetrievePoolDetails(ctx context.Context, sym string) (*dataserviceapi.PoolDetails, error) {
if as.UseApi {
return retrievePoolDetails(ctx, sym)
} else {
return nil, nil
}
}
func retrievePoolDetails(ctx context.Context, sym string) (*dataserviceapi.PoolDetails, error) {
var r struct {
PoolDetails dataserviceapi.PoolDetails `json:"poolDetails"`
}
ep, err := url.JoinPath(config.RetrievePoolDetailsURL, sym)
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.PoolDetails, nil
}
func (as *HTTPAccountService) PoolDeposit(ctx context.Context, amount, from, poolAddress, tokenAddress string) (*models.PoolDepositResult, error) { func (as *HTTPAccountService) PoolDeposit(ctx context.Context, amount, from, poolAddress, tokenAddress string) (*models.PoolDepositResult, error) {
var r models.PoolDepositResult var r models.PoolDepositResult
@ -310,6 +386,7 @@ func (as *HTTPAccountService) GetPoolSwapQuote(ctx context.Context, amount, from
if err != nil { if err != nil {
return nil, err return nil, err
} }
req, err := http.NewRequest("POST", config.PoolSwapQuoteURL, bytes.NewBuffer(payloadBytes)) req, err := http.NewRequest("POST", config.PoolSwapQuoteURL, bytes.NewBuffer(payloadBytes))
if err != nil { if err != nil {
return nil, err return nil, err
@ -322,13 +399,85 @@ func (as *HTTPAccountService) GetPoolSwapQuote(ctx context.Context, amount, from
} }
func (as *HTTPAccountService) GetPoolSwappableFromVouchers(ctx context.Context, poolAddress, publicKey string) ([]dataserviceapi.TokenHoldings, error) { func (as *HTTPAccountService) GetPoolSwappableFromVouchers(ctx context.Context, poolAddress, publicKey string) ([]dataserviceapi.TokenHoldings, error) {
if as.UseApi {
return as.getPoolSwappableFromVouchers(ctx, poolAddress, publicKey)
} else {
svc := dev.NewDevAccountService(ctx, as.SS) svc := dev.NewDevAccountService(ctx, as.SS)
return svc.GetPoolSwappableFromVouchers(ctx, poolAddress, publicKey) return svc.GetPoolSwappableFromVouchers(ctx, poolAddress, publicKey)
} }
}
func (as *HTTPAccountService) GetPoolSwappableVouchers(ctx context.Context, poolAddress, publicKey string) ([]dataserviceapi.TokenHoldings, error) { func (as *HTTPAccountService) getPoolSwappableFromVouchers(ctx context.Context, poolAddress, publicKey string) ([]dataserviceapi.TokenHoldings, error) {
var r struct {
PoolSwappableVouchers []dataserviceapi.TokenHoldings `json:"filtered"`
}
ep, err := url.JoinPath(config.PoolSwappableVouchersURL, poolAddress, "from", 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
}
// Normalize symbols before returning
for i := range r.PoolSwappableVouchers {
r.PoolSwappableVouchers[i].TokenSymbol = sanitizeSymbol(r.PoolSwappableVouchers[i].TokenSymbol)
}
return r.PoolSwappableVouchers, nil
}
func (as *HTTPAccountService) GetPoolSwappableVouchers(ctx context.Context, poolAddress string) ([]dataserviceapi.TokenHoldings, error) {
svc := dev.NewDevAccountService(ctx, as.SS) svc := dev.NewDevAccountService(ctx, as.SS)
return svc.GetPoolSwappableVouchers(ctx, poolAddress, publicKey) if as.UseApi {
return as.getPoolSwappableVouchers(ctx, poolAddress)
} else {
return svc.GetPoolSwappableVouchers(ctx, poolAddress)
}
}
func (as HTTPAccountService) getPoolSwappableVouchers(ctx context.Context, poolAddress string) ([]dataserviceapi.TokenHoldings, error) {
var r struct {
PoolSwappableVouchers []dataserviceapi.TokenHoldings `json:"filtered"`
}
basePath, err := url.JoinPath(config.PoolSwappableVouchersURL, poolAddress, "to")
if err != nil {
return nil, err
}
parsedURL, err := url.Parse(basePath)
if err != nil {
return nil, err
}
query := parsedURL.Query()
if config.IncludeStablesParam != "" {
query.Set("stables", config.IncludeStablesParam)
}
parsedURL.RawQuery = query.Encode()
req, err := http.NewRequest("GET", parsedURL.String(), nil)
if err != nil {
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
} }
func (as *HTTPAccountService) PoolSwap(ctx context.Context, amount, from, fromTokenAddress, poolAddress, toTokenAddress string) (*models.PoolSwapResult, error) { func (as *HTTPAccountService) PoolSwap(ctx context.Context, amount, from, fromTokenAddress, poolAddress, toTokenAddress string) (*models.PoolSwapResult, error) {
@ -347,7 +496,8 @@ func (as *HTTPAccountService) PoolSwap(ctx context.Context, amount, from, fromTo
if err != nil { if err != nil {
return nil, err return nil, err
} }
req, err := http.NewRequest("POST", config.PoolSwapQuoteURL, bytes.NewBuffer(payloadBytes))
req, err := http.NewRequest("POST", config.PoolSwapURL, bytes.NewBuffer(payloadBytes))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -359,10 +509,62 @@ func (as *HTTPAccountService) PoolSwap(ctx context.Context, amount, from, fromTo
} }
func (as *HTTPAccountService) GetSwapFromTokenMaxLimit(ctx context.Context, poolAddress, fromTokenAddress, toTokenAddress, publicKey string) (*models.MaxLimitResult, error) { func (as *HTTPAccountService) GetSwapFromTokenMaxLimit(ctx context.Context, poolAddress, fromTokenAddress, toTokenAddress, publicKey string) (*models.MaxLimitResult, error) {
if as.UseApi {
return as.getSwapFromTokenMaxLimit(ctx, poolAddress, fromTokenAddress, toTokenAddress, publicKey)
} else {
svc := dev.NewDevAccountService(ctx, as.SS) svc := dev.NewDevAccountService(ctx, as.SS)
return svc.GetSwapFromTokenMaxLimit(ctx, poolAddress, fromTokenAddress, toTokenAddress, publicKey) return svc.GetSwapFromTokenMaxLimit(ctx, poolAddress, fromTokenAddress, toTokenAddress, publicKey)
} }
}
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, 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
}
func (as *HTTPAccountService) CheckTokenInPool(ctx context.Context, poolAddress, tokenAddress string) (*models.TokenInPoolResult, error) {
if as.UseApi {
return as.checkTokenInPool(ctx, poolAddress, tokenAddress)
} else {
svc := dev.NewDevAccountService(ctx, as.SS)
return svc.CheckTokenInPool(ctx, poolAddress, tokenAddress)
}
}
func (as *HTTPAccountService) checkTokenInPool(ctx context.Context, poolAddress, tokenAddress string) (*models.TokenInPoolResult, error) {
var r models.TokenInPoolResult
ep, err := url.JoinPath(config.PoolSwappableVouchersURL, poolAddress, "check", tokenAddress)
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: Use actual custodial api to request available alias
func (as *HTTPAccountService) RequestAlias(ctx context.Context, publicKey string, hint string) (*models.RequestAliasResult, error) { func (as *HTTPAccountService) RequestAlias(ctx context.Context, publicKey string, hint string) (*models.RequestAliasResult, error) {
if as.SS == nil { if as.SS == nil {
return nil, fmt.Errorf("The storage service cannot be nil") return nil, fmt.Errorf("The storage service cannot be nil")
@ -385,11 +587,9 @@ func (as *HTTPAccountService) RequestAlias(ctx context.Context, publicKey string
func requestEnsAlias(ctx context.Context, publicKey string, hint string) (*models.AliasEnsResult, error) { func requestEnsAlias(ctx context.Context, publicKey string, hint string) (*models.AliasEnsResult, error) {
var r models.AliasEnsResult var r models.AliasEnsResult
ep, err := url.JoinPath(config.AliasEnsURL, "/register") endpoint := config.AliasRegistrationURL
if err != nil {
return nil, err logg.InfoCtxf(ctx, "requesting alias", "endpoint", endpoint, "hint", hint)
}
logg.InfoCtxf(ctx, "requesting alias", "endpoint", ep, "hint", hint)
//Payload with the address and hint to derive an ENS name //Payload with the address and hint to derive an ENS name
payload := map[string]string{ payload := map[string]string{
"address": publicKey, "address": publicKey,
@ -399,7 +599,7 @@ func requestEnsAlias(ctx context.Context, publicKey string, hint string) (*model
if err != nil { if err != nil {
return nil, err return nil, err
} }
req, err := http.NewRequest("POST", ep, bytes.NewBuffer(payloadBytes)) req, err := http.NewRequest("POST", endpoint, bytes.NewBuffer(payloadBytes))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -413,6 +613,175 @@ func requestEnsAlias(ctx context.Context, publicKey string, hint string) (*model
return &r, nil return &r, nil
} }
func (as *HTTPAccountService) UpdateAlias(ctx context.Context, name string, publicKey string) (*models.RequestAliasResult, error) {
if as.SS == nil {
return nil, fmt.Errorf("The storage service cannot be nil")
}
if as.UseApi {
if !strings.Contains(name, ".") {
name = as.ToFqdn(name)
}
enr, err := updateEnsAlias(ctx, name, publicKey)
if err != nil {
return nil, err
}
return &models.RequestAliasResult{Alias: enr.Name}, nil
} else {
svc := dev.NewDevAccountService(ctx, as.SS)
return svc.RequestAlias(ctx, publicKey, name)
}
}
func updateEnsAlias(ctx context.Context, name string, publicKey string) (*models.AliasEnsResult, error) {
var r models.AliasEnsResult
endpoint := config.AliasUpdateURL
logg.InfoCtxf(ctx, "updating alias", "endpoint", endpoint, "name", name)
payload := map[string]string{
"name": name,
"address": publicKey,
}
payloadBytes, err := json.Marshal(payload)
if err != nil {
return nil, err
}
req, err := http.NewRequest("PUT", endpoint, bytes.NewBuffer(payloadBytes))
if err != nil {
return nil, err
}
// Log the request body
logg.InfoCtxf(ctx, "request body", "payload", string(payloadBytes))
_, err = doRequest(ctx, req, &r)
if err != nil {
return nil, err
}
logg.InfoCtxf(ctx, "alias successfully updated", "alias", r.Name)
return &r, nil
}
// SendSMS calls the API to send out an SMS.
// Parameters:
// - inviterPhone: The user initiating the SMS.
// - inviteePhone: The number being invited to Sarafu.
func (as *HTTPAccountService) SendUpsellSMS(ctx context.Context, inviterPhone, inviteePhone string) (*models.SendSMSResponse, error) {
var r models.SendSMSResponse
// Create request payload
payload := map[string]string{
"inviterPhone": inviterPhone,
"inviteePhone": inviteePhone,
}
payloadBytes, err := json.Marshal(payload)
if err != nil {
return nil, err
}
// Create a new request
req, err := http.NewRequest("POST", config.SendSMSURL, bytes.NewBuffer(payloadBytes))
if err != nil {
return nil, err
}
_, err = doRequest(ctx, req, &r)
if err != nil {
return nil, err
}
return &r, nil
}
func (as *HTTPAccountService) SendAddressSMS(ctx context.Context, publicKey, originPhone string) error {
ep, err := url.JoinPath(config.ExternalSMSURL, "address")
if err != nil {
return err
}
logg.InfoCtxf(ctx, "sending an address sms", "endpoint", ep, "address", publicKey, "origin-phone", originPhone)
payload := map[string]string{
"address": publicKey,
"originPhone": originPhone,
}
payloadBytes, err := json.Marshal(payload)
if err != nil {
return err
}
req, err := http.NewRequest("POST", ep, bytes.NewBuffer(payloadBytes))
if err != nil {
return err
}
_, err = doRequest(ctx, req, nil)
if err != nil {
return err
}
return nil
}
func (as *HTTPAccountService) SendPINResetSMS(ctx context.Context, admin, phone string) error {
ep, err := url.JoinPath(config.ExternalSMSURL, "pinreset")
if err != nil {
return err
}
logg.InfoCtxf(ctx, "sending pin reset sms", "endpoint", ep, "admin", admin, "phone", phone)
payload := map[string]string{
"admin": admin,
"phone": phone,
}
payloadBytes, err := json.Marshal(payload)
if err != nil {
return err
}
req, err := http.NewRequest("POST", ep, bytes.NewBuffer(payloadBytes))
if err != nil {
return err
}
_, err = doRequest(ctx, req, nil)
if err != nil {
return err
}
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 // TODO: remove eth-custodial api dependency
func doRequest(ctx context.Context, req *http.Request, rcpt any) (*api.OKResponse, error) { func doRequest(ctx context.Context, req *http.Request, rcpt any) (*api.OKResponse, error) {
var okResponse api.OKResponse var okResponse api.OKResponse
@ -421,6 +790,7 @@ func doRequest(ctx context.Context, req *http.Request, rcpt any) (*api.OKRespons
req.Header.Set("Authorization", "Bearer "+config.BearerToken) req.Header.Set("Authorization", "Bearer "+config.BearerToken)
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
// Log request
logRequestDetails(req) logRequestDetails(req)
resp, err := http.DefaultClient.Do(req) resp, err := http.DefaultClient.Do(req)
@ -431,24 +801,32 @@ func doRequest(ctx context.Context, req *http.Request, rcpt any) (*api.OKRespons
} }
defer resp.Body.Close() defer resp.Body.Close()
log.Printf("Received response for %s: Status Code: %d | Content-Type: %s", req.URL, resp.StatusCode, resp.Header.Get("Content-Type")) // Read and log response body
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Printf("Received response for %s: Status Code: %d | Content-Type: %s | Body: %s",
req.URL, resp.StatusCode, resp.Header.Get("Content-Type"), string(body))
if resp.StatusCode >= http.StatusBadRequest { if resp.StatusCode >= http.StatusBadRequest {
err := json.Unmarshal([]byte(body), &errResponse) if err := json.Unmarshal(body, &errResponse); err != nil {
if err != nil {
return nil, err return nil, err
} }
return nil, errors.New(errResponse.Description)
return nil, &APIError{
Code: errResponse.ErrCode,
Description: errResponse.Description,
} }
err = json.Unmarshal([]byte(body), &okResponse) }
if err != nil {
if err := json.Unmarshal(body, &okResponse); err != nil {
return nil, err return nil, err
} }
if len(okResponse.Result) == 0 { 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) v, err := json.Marshal(okResponse.Result)
@ -463,16 +841,14 @@ func doRequest(ctx context.Context, req *http.Request, rcpt any) (*api.OKRespons
func logRequestDetails(req *http.Request) { func logRequestDetails(req *http.Request) {
var bodyBytes []byte var bodyBytes []byte
contentType := req.Header.Get("Content-Type") contentType := req.Header.Get("Content-Type")
if req.Body != nil { if req.Body != nil {
bodyBytes, err := io.ReadAll(req.Body) bodyBytes, _ = io.ReadAll(req.Body)
if err != nil { req.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) // Restore body
log.Printf("Error reading request body: %s", err)
return
}
req.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
} else { } else {
bodyBytes = []byte("-") bodyBytes = []byte("-")
} }
log.Printf("URL: %s | Content-Type: %s | Method: %s| Request Body: %s", req.URL, contentType, req.Method, string(bodyBytes)) log.Printf("Outgoing Request -> URL: %s | Method: %s | Content-Type: %s | Body: %s",
req.URL.String(), req.Method, contentType, string(bodyBytes))
} }

View File

@ -3,13 +3,13 @@ package mocks
import ( import (
"context" "context"
"git.defalsify.org/vise.git/logging"
"git.grassecon.net/grassrootseconomics/sarafu-api/models" "git.grassecon.net/grassrootseconomics/sarafu-api/models"
slogging "github.com/grassrootseconomics/go-vise/slog"
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
) )
var ( var (
logg = logging.NewVanilla().WithDomain("sarafu-vise-events.testutil") logg = slogging.Get().With("component", "sarafu-vise-events.testutil")
) )
const ( const (
@ -58,6 +58,14 @@ func (m MockApi) RequestAlias(ctx context.Context, publicKey string, hint string
return nil, nil return nil, nil
} }
func (m MockApi) UpdateAlias(ctx context.Context, publicKey string, name string) (*models.RequestAliasResult, error) {
return nil, nil
}
func (m MockApi) TokenTransfer(ctx context.Context, amount, from, to, tokenAddress string) (*models.TokenTransferResponse, error) { func (m MockApi) TokenTransfer(ctx context.Context, amount, from, to, tokenAddress string) (*models.TokenTransferResponse, error) {
return nil, nil return nil, nil
} }
func (m MockApi) SendUpsellSMS(ctx context.Context, inviterPhone, inviteePhone string) (*models.SendSMSResponse, error) {
return nil, nil
}

View File

@ -53,11 +53,29 @@ func (m *MockAccountService) CheckAliasAddress(ctx context.Context, alias string
return args.Get(0).(*models.AliasAddress), args.Error(1) return args.Get(0).(*models.AliasAddress), args.Error(1)
} }
func (m MockAccountService) RequestAlias(ctx context.Context, publicKey string, hint string) (*models.RequestAliasResult, error) { func (m *MockAccountService) RequestAlias(ctx context.Context, publicKey string, hint string) (*models.RequestAliasResult, error) {
args := m.Called(publicKey, hint) args := m.Called(publicKey, hint)
return args.Get(0).(*models.RequestAliasResult), args.Error(1) return args.Get(0).(*models.RequestAliasResult), args.Error(1)
} }
func (m *MockAccountService) UpdateAlias(ctx context.Context, publicKey string, name string) (*models.RequestAliasResult, error) {
args := m.Called(publicKey, name)
return args.Get(0).(*models.RequestAliasResult), args.Error(1)
}
func (m *MockAccountService) SendUpsellSMS(ctx context.Context, inviterPhone, inviteePhone string) (*models.SendSMSResponse, error) {
args := m.Called(inviterPhone, inviteePhone)
return args.Get(0).(*models.SendSMSResponse), args.Error(1)
}
func (m *MockAccountService) SendPINResetSMS(ctx context.Context, admin, phone string) error {
return nil
}
func (m *MockAccountService) SendAddressSMS(ctx context.Context, publicKey, originPhone string) error {
return nil
}
func (m MockAccountService) PoolDeposit(ctx context.Context, amount, from, poolAddress, tokenAddress string) (*models.PoolDepositResult, error) { func (m MockAccountService) PoolDeposit(ctx context.Context, amount, from, poolAddress, tokenAddress string) (*models.PoolDepositResult, error) {
args := m.Called(amount, from, poolAddress, tokenAddress) args := m.Called(amount, from, poolAddress, tokenAddress)
return args.Get(0).(*models.PoolDepositResult), args.Error(1) return args.Get(0).(*models.PoolDepositResult), args.Error(1)
@ -68,13 +86,18 @@ func (m MockAccountService) FetchTopPools(ctx context.Context) ([]dataserviceapi
return args.Get(0).([]dataserviceapi.PoolDetails), args.Error(1) return args.Get(0).([]dataserviceapi.PoolDetails), args.Error(1)
} }
func (m MockAccountService) RetrievePoolDetails(ctx context.Context, sym string) (*dataserviceapi.PoolDetails, error) {
args := m.Called()
return args.Get(0).(*dataserviceapi.PoolDetails), args.Error(1)
}
func (m MockAccountService) GetPoolSwappableFromVouchers(ctx context.Context, poolAddress, publicKey string) ([]dataserviceapi.TokenHoldings, error) { func (m MockAccountService) GetPoolSwappableFromVouchers(ctx context.Context, poolAddress, publicKey string) ([]dataserviceapi.TokenHoldings, error) {
args := m.Called(poolAddress, publicKey) args := m.Called(poolAddress, publicKey)
return args.Get(0).([]dataserviceapi.TokenHoldings), args.Error(1) return args.Get(0).([]dataserviceapi.TokenHoldings), args.Error(1)
} }
func (m MockAccountService) GetPoolSwappableVouchers(ctx context.Context, poolAddress, publicKey string) ([]dataserviceapi.TokenHoldings, error) { func (m MockAccountService) GetPoolSwappableVouchers(ctx context.Context, poolAddress string) ([]dataserviceapi.TokenHoldings, error) {
args := m.Called(poolAddress, publicKey) args := m.Called(poolAddress)
return args.Get(0).([]dataserviceapi.TokenHoldings), args.Error(1) return args.Get(0).([]dataserviceapi.TokenHoldings), args.Error(1)
} }
@ -92,3 +115,18 @@ func (m MockAccountService) GetSwapFromTokenMaxLimit(ctx context.Context, poolAd
args := m.Called(poolAddress, fromTokenAddress, toTokenAddress, publicKey) args := m.Called(poolAddress, fromTokenAddress, toTokenAddress, publicKey)
return args.Get(0).(*models.MaxLimitResult), args.Error(1) return args.Get(0).(*models.MaxLimitResult), args.Error(1)
} }
func (m MockAccountService) CheckTokenInPool(ctx context.Context, poolAddress, tokenAddress string) (*models.TokenInPoolResult, error) {
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" dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
) )
// This is used in the menu traversal tests
type TestAccountService struct { 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) { func (tas *TestAccountService) FetchVouchers(ctx context.Context, publicKey string) ([]dataserviceapi.TokenHoldings, error) {
return []dataserviceapi.TokenHoldings{ return []dataserviceapi.TokenHoldings{
dataserviceapi.TokenHoldings{ {
ContractAddress: "0x6CC75A06ac72eB4Db2eE22F781F5D100d8ec03ee", TokenAddress: "0x6CC75A06ac72eB4Db2eE22F781F5D100d8ec03ee",
TokenSymbol: "SRF", TokenSymbol: "SRF",
TokenDecimals: "6", TokenDecimals: "6",
Balance: "2745987", Balance: "2745987",
}, },
{
TokenAddress: "0x3f195a3F68BF4c6D49748eFa033a00C6634fF311",
TokenSymbol: "USD",
TokenDecimals: "6",
Balance: "4269100",
},
}, nil }, nil
} }
@ -61,23 +69,43 @@ func (m TestAccountService) PoolDeposit(ctx context.Context, amount, from, poolA
return &models.PoolDepositResult{}, nil return &models.PoolDepositResult{}, nil
} }
func (m TestAccountService) CheckAliasAddress(ctx context.Context, alias string) (*models.AliasAddress, error) { func (m *TestAccountService) CheckAliasAddress(ctx context.Context, alias string) (*models.AliasAddress, error) {
return &models.AliasAddress{}, nil return &models.AliasAddress{}, nil
} }
func (m TestAccountService) RequestAlias(ctx context.Context, publicKey string, hint string) (*models.RequestAliasResult, error) { func (m *TestAccountService) RequestAlias(ctx context.Context, publicKey string, hint string) (*models.RequestAliasResult, error) {
return &models.RequestAliasResult{}, nil return &models.RequestAliasResult{}, nil
} }
func (m *TestAccountService) UpdateAlias(ctx context.Context, publicKey string, hint string) (*models.RequestAliasResult, error) {
return &models.RequestAliasResult{}, nil
}
func (m *TestAccountService) SendUpsellSMS(ctx context.Context, inviterPhone, inviteePhone string) (*models.SendSMSResponse, error) {
return &models.SendSMSResponse{}, nil
}
func (m *TestAccountService) SendAddressSMS(ctx context.Context, publicKey, originPhone string) error {
return nil
}
func (m *TestAccountService) SendPINResetSMS(ctx context.Context, admin, phone string) error {
return nil
}
func (m TestAccountService) FetchTopPools(ctx context.Context) ([]dataserviceapi.PoolDetails, error) { func (m TestAccountService) FetchTopPools(ctx context.Context) ([]dataserviceapi.PoolDetails, error) {
return []dataserviceapi.PoolDetails{}, nil return []dataserviceapi.PoolDetails{}, nil
} }
func (m TestAccountService) RetrievePoolDetails(ctx context.Context, sym string) (*dataserviceapi.PoolDetails, error) {
return &dataserviceapi.PoolDetails{}, nil
}
func (m TestAccountService) GetPoolSwappableFromVouchers(ctx context.Context, poolAddress, publicKey string) ([]dataserviceapi.TokenHoldings, error) { func (m TestAccountService) GetPoolSwappableFromVouchers(ctx context.Context, poolAddress, publicKey string) ([]dataserviceapi.TokenHoldings, error) {
return []dataserviceapi.TokenHoldings{}, nil return []dataserviceapi.TokenHoldings{}, nil
} }
func (m TestAccountService) GetPoolSwappableVouchers(ctx context.Context, poolAddress, publicKey string) ([]dataserviceapi.TokenHoldings, error) { func (m TestAccountService) GetPoolSwappableVouchers(ctx context.Context, poolAddress string) ([]dataserviceapi.TokenHoldings, error) {
return []dataserviceapi.TokenHoldings{}, nil return []dataserviceapi.TokenHoldings{}, nil
} }
@ -92,3 +120,15 @@ func (m TestAccountService) PoolSwap(ctx context.Context, amount, from, fromToke
func (m TestAccountService) GetSwapFromTokenMaxLimit(ctx context.Context, poolAddress, fromTokenAddress, toTokenAddress, publicKey string) (*models.MaxLimitResult, error) { func (m TestAccountService) GetSwapFromTokenMaxLimit(ctx context.Context, poolAddress, fromTokenAddress, toTokenAddress, publicKey string) (*models.MaxLimitResult, error) {
return &models.MaxLimitResult{}, nil return &models.MaxLimitResult{}, nil
} }
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
}