Merge pull request 'pool-swap-endpoints' (#11) from pool-swap-endpoints into master

Reviewed-on: #11
This commit is contained in:
Alfred Kamanda 2025-06-26 07:58:53 +02:00
commit d177942bd2
12 changed files with 782 additions and 70 deletions

View File

@ -7,41 +7,54 @@ import (
) )
const ( const (
createAccountPath = "/api/v2/account/create" createAccountPath = "/api/v2/account/create"
trackStatusPath = "/api/track" trackStatusPath = "/api/track"
balancePathPrefix = "/api/account" balancePathPrefix = "/api/account"
trackPath = "/api/v2/account/status" trackPath = "/api/v2/account/status"
tokenTransferPrefix = "/api/v2/token/transfer" tokenTransferPrefix = "/api/v2/token/transfer"
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"
SendSMSPrefix = "api/v1/external/upsell" poolDepositPrefix = "/api/v2/pool/deposit"
AliasEnsPrefix = "/api/v1/bypass" poolSwapQoutePrefix = "/api/v2/pool/quote"
ExternalSMSPrefix = "/api/v1/external" 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"
) )
var ( var (
custodialURLBase string custodialURLBase string
dataURLBase string dataURLBase string
BearerToken string BearerToken string
aliasEnsURLBase string aliasEnsURLBase string
externalSMSBase string externalSMSBase string
IncludeStablesParam string
) )
var ( var (
CreateAccountURL string CreateAccountURL string
TrackStatusURL string TrackStatusURL string
BalanceURL string BalanceURL string
TrackURL string TrackURL string
TokenTransferURL string TokenTransferURL string
VoucherHoldingsURL string VoucherHoldingsURL string
VoucherTransfersURL string VoucherTransfersURL string
VoucherDataURL string VoucherDataURL string
CheckAliasURL string PoolDepositURL string
SendSMSURL string PoolSwapQuoteURL string
AliasEnsURL string PoolSwapURL string
ExternalSMSURL string TopPoolsURL string
RetrievePoolDetailsURL string
PoolSwappableVouchersURL string
SendSMSURL string
AliasRegistrationURL string
AliasResolverURL string
ExternalSMSURL string
) )
func setBase() error { func setBase() error {
@ -52,6 +65,7 @@ func setBase() error {
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") 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,9 +92,15 @@ 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) SendSMSURL, _ = url.JoinPath(dataURLBase, SendSMSPrefix)
AliasEnsURL, _ = url.JoinPath(aliasEnsURLBase, AliasEnsPrefix) PoolDepositURL, _ = url.JoinPath(custodialURLBase, poolDepositPrefix)
PoolSwapQuoteURL, _ = url.JoinPath(custodialURLBase, poolSwapQoutePrefix)
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) ExternalSMSURL, _ = url.JoinPath(externalSMSBase, ExternalSMSPrefix)
return nil return nil

View File

@ -34,6 +34,9 @@ const (
defaultDecimals = 6 defaultDecimals = 6
zeroAddress string = "0x0000000000000000000000000000000000000000" zeroAddress string = "0x0000000000000000000000000000000000000000"
defaultVoucherBalance float64 = 500.00 defaultVoucherBalance float64 = 500.00
cityPoolAddress string = "0x3b517308D858a47458aD5C8E699697C5dc91Da0F"
poolName string = "citypool"
PoolSymbol string = "CTY"
) )
type Tx struct { type Tx struct {
@ -91,6 +94,14 @@ type Voucher struct {
Location string `json: "location"` Location string `json: "location"`
} }
type Pool struct {
Name string `json: "name"`
Symbol string `json: "symbol"`
Address string `json: "address"`
Vouchers []Voucher `json: "voucher"`
PoolLimit map[string]string `json: "poollimit"`
}
type DevAccountService struct { type DevAccountService struct {
db db.Db db db.Db
accounts map[string]Account accounts map[string]Account
@ -106,6 +117,7 @@ type DevAccountService struct {
defaultAccount string defaultAccount string
emitterFunc event.EmitterFunc emitterFunc event.EmitterFunc
pfx []byte pfx []byte
pools map[string]Pool
} }
func NewDevAccountService(ctx context.Context, ss storage.StorageService) *DevAccountService { func NewDevAccountService(ctx context.Context, ss storage.StorageService) *DevAccountService {
@ -118,6 +130,7 @@ func NewDevAccountService(ctx context.Context, ss storage.StorageService) *DevAc
txs: make(map[string]Tx), txs: make(map[string]Tx),
txsTrack: make(map[string]string), txsTrack: make(map[string]string),
autoVoucherValue: make(map[string]int), autoVoucherValue: make(map[string]int),
pools: make(map[string]Pool),
defaultAccount: zeroAddress, defaultAccount: zeroAddress,
pfx: []byte("__"), pfx: []byte("__"),
} }
@ -171,6 +184,15 @@ func (das *DevAccountService) loadAccount(ctx context.Context, pubKey string, v
return nil return nil
} }
func (p *Pool) hasVoucher(voucherAddress string) bool {
for _, value := range p.Vouchers {
if value.Address == voucherAddress {
return true
}
}
return false
}
func (das *DevAccountService) loadTx(ctx context.Context, hsh string, v []byte) error { func (das *DevAccountService) loadTx(ctx context.Context, hsh string, v []byte) error {
var mytx Tx var mytx Tx
@ -193,6 +215,17 @@ func (das *DevAccountService) loadAlias(ctx context.Context, alias string, key [
return nil return nil
} }
func (das *DevAccountService) loadPoolInfo(ctx context.Context, name string, v []byte) error {
var pool Pool
err := json.Unmarshal(v, &pool)
if err != nil {
return fmt.Errorf("failed to unmarshall pool info: %v", err)
}
das.pools[name] = pool
return nil
}
func (das *DevAccountService) loadItem(ctx context.Context, k []byte, v []byte) error { func (das *DevAccountService) loadItem(ctx context.Context, k []byte, v []byte) error {
var err error var err error
s := string(k) s := string(k)
@ -209,6 +242,8 @@ func (das *DevAccountService) loadItem(ctx context.Context, k []byte, v []byte)
} else if ss[0] == "alias" { } else if ss[0] == "alias" {
err = das.loadAlias(ctx, ss[1], k) err = das.loadAlias(ctx, ss[1], k)
logg.ErrorCtxf(ctx, "loading aliases failed", "error_load_aliases", err) logg.ErrorCtxf(ctx, "loading aliases failed", "error_load_aliases", err)
} else if ss[0] == "pool" {
err = das.loadPoolInfo(ctx, ss[1], v)
} else { } else {
logg.ErrorCtxf(ctx, "unknown double underscore key", "key", ss[0]) logg.ErrorCtxf(ctx, "unknown double underscore key", "key", ss[0])
} }
@ -224,13 +259,13 @@ func (das *DevAccountService) loadAll(ctx context.Context) error {
} }
for true { for true {
k, v := dumper.Next(ctx) k, v := dumper.Next(ctx)
logg.InfoCtxf(ctx, "loading all", "key", string(k), "value", string(v))
if k == nil { if k == nil {
break break
} }
if !bytes.HasPrefix(k, das.pfx) { if !bytes.HasPrefix(k, das.pfx) {
continue continue
} }
err = das.loadItem(ctx, k, v) err = das.loadItem(ctx, k, v)
if err != nil { if err != nil {
return err return err
@ -265,6 +300,35 @@ func (das *DevAccountService) WithAutoVoucher(ctx context.Context, symbol string
return das return das
} }
func (das *DevAccountService) RegisterPool(ctx context.Context, name string, sm string) error {
var seedVouchers []Voucher
h := sha1.New()
h.Write([]byte(sm))
z := h.Sum(nil)
pooladdr := fmt.Sprintf("0x%x", z)
p := Pool{
Name: name,
Symbol: sm,
Address: pooladdr,
PoolLimit: make(map[string]string),
}
for _, v := range das.vouchers {
//pre-load vouchers with vouchers when a pool is registered
seedVouchers = append(seedVouchers, v)
p.PoolLimit[v.Address] = fmt.Sprintf("%f", defaultVoucherBalance)
}
p.Vouchers = append(p.Vouchers, seedVouchers...)
err := das.savePoolInfo(ctx, p)
if err != nil {
return err
}
return nil
}
// TODO: add persistence for vouchers // TODO: add persistence for vouchers
// TODO: set max balance for 0x00 address // TODO: set max balance for 0x00 address
func (das *DevAccountService) AddVoucher(ctx context.Context, symbol string) error { func (das *DevAccountService) AddVoucher(ctx context.Context, symbol string) error {
@ -345,6 +409,20 @@ func (das *DevAccountService) saveAccount(ctx context.Context, acc Account) erro
return das.db.Put(ctx, []byte(k), v) return das.db.Put(ctx, []byte(k), v)
} }
func (das *DevAccountService) savePoolInfo(ctx context.Context, pool Pool) error {
if das.db == nil {
return nil
}
k := das.prefixKeyFor("pool", pool.Address)
v, err := json.Marshal(pool)
if err != nil {
return err
}
das.db.SetSession("")
das.db.SetPrefix(db.DATATYPE_USERDATA)
return das.db.Put(ctx, []byte(k), v)
}
func (das *DevAccountService) saveAlias(ctx context.Context, alias map[string]string) error { func (das *DevAccountService) saveAlias(ctx context.Context, alias map[string]string) error {
if das.db == nil { if das.db == nil {
return fmt.Errorf("Db cannot be nil") return fmt.Errorf("Db cannot be nil")
@ -415,6 +493,80 @@ func (das *DevAccountService) CreateAccount(ctx context.Context) (*models.Accoun
}, nil }, nil
} }
func (das *DevAccountService) PoolDeposit(ctx context.Context, amount, from, poolAddress, tokenAddress string) (*models.PoolDepositResult, error) {
_, ok := das.accounts[from]
if !ok {
return nil, fmt.Errorf("account not found (publickey): %v", from)
}
sym, ok := das.vouchersAddress[tokenAddress]
if !ok {
return nil, fmt.Errorf("voucher address %v not found", tokenAddress)
}
uid, err := uuid.NewV4()
if err != nil {
return nil, err
}
_, ok = das.vouchers[sym]
if !ok {
return nil, fmt.Errorf("voucher address %v found but does not resolve", tokenAddress)
}
if err != nil {
return nil, err
}
return &models.PoolDepositResult{
TrackingId: uid.String(),
}, nil
}
func (das *DevAccountService) GetPoolSwapQuote(ctx context.Context, amount, from, fromTokenAddress, poolAddress, toTokenAddress string) (*models.PoolSwapQuoteResult, error) {
_, ok := das.accounts[from]
if !ok {
return nil, fmt.Errorf("account not found (publickey): %v", from)
}
p, ok := das.pools[poolAddress]
if !ok {
return nil, fmt.Errorf("pool address %v not found", poolAddress)
}
//resolve the token address you are trying to swap from(fromTokenAddress)
ok = p.hasVoucher(fromTokenAddress)
if !ok {
return nil, fmt.Errorf("voucher with address %v not found in the pool", fromTokenAddress)
}
//Return a a quote that is equal to the amount entered
return &models.PoolSwapQuoteResult{IncludesFeesDeduction: false, OutValue: amount}, nil
}
func (das *DevAccountService) PoolSwap(ctx context.Context, amount, from, fromTokenAddress, poolAddress, toTokenAddress string) (*models.PoolSwapResult, error) {
uid, err := uuid.NewV4()
if err != nil {
return nil, err
}
p, ok := das.pools[poolAddress]
if !ok {
return nil, fmt.Errorf("pool address %v not found", toTokenAddress)
}
_, ok = das.accounts[from]
if !ok {
return nil, fmt.Errorf("account not found (publickey): %v", from)
}
ok = p.hasVoucher(fromTokenAddress)
if !ok {
return nil, fmt.Errorf("token %v not found in the pool", fromTokenAddress)
}
ok = p.hasVoucher(toTokenAddress)
if !ok {
return nil, fmt.Errorf("token %v not found in the pool", toTokenAddress)
}
return &models.PoolSwapResult{TrackingId: uid.String()}, nil
}
func (das *DevAccountService) TrackAccountStatus(ctx context.Context, publicKey string) (*models.TrackStatusResult, error) { func (das *DevAccountService) TrackAccountStatus(ctx context.Context, publicKey string) (*models.TrackStatusResult, error) {
var ok bool var ok bool
_, ok = das.accounts[publicKey] _, ok = das.accounts[publicKey]
@ -435,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)),
@ -668,3 +820,83 @@ func (das *DevAccountService) SendPINResetSMS(ctx context.Context, admin, phone
func (das *DevAccountService) SendAddressSMS(ctx context.Context, publicKey, originPhone string) error { func (das *DevAccountService) SendAddressSMS(ctx context.Context, publicKey, originPhone string) error {
return fmt.Errorf("unimplemented") return fmt.Errorf("unimplemented")
} }
func (das *DevAccountService) FetchTopPools(ctx context.Context) ([]dataserviceapi.PoolDetails, error) {
var topPools []dataserviceapi.PoolDetails
for _, p := range das.pools {
topPools = append(topPools, dataserviceapi.PoolDetails{
PoolName: p.Name,
PoolSymbol: p.Symbol,
PoolContractAdrress: p.Address,
})
}
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) {
var swapFromList []dataserviceapi.TokenHoldings
p, ok := das.pools[poolAddress]
if !ok {
return nil, fmt.Errorf("Invalid pool address: %v", poolAddress)
}
for _, v := range p.Vouchers {
swapFromList = append(swapFromList, dataserviceapi.TokenHoldings{
TokenAddress: v.Address,
TokenSymbol: v.Symbol,
TokenDecimals: string(defaultDecimals),
Balance: fmt.Sprintf("%f", defaultVoucherBalance),
})
}
return swapFromList, nil
}
func (das *DevAccountService) GetPoolSwappableVouchers(ctx context.Context, poolAddress string) ([]dataserviceapi.TokenHoldings, error) {
var swapToList []dataserviceapi.TokenHoldings
_, ok := das.pools[poolAddress]
if !ok {
return nil, fmt.Errorf("Invalid pool address: %v", poolAddress)
}
for _, voucher := range das.vouchers {
swapToList = append(swapToList, dataserviceapi.TokenHoldings{
TokenAddress: voucher.Address,
TokenSymbol: voucher.Symbol,
TokenDecimals: strconv.Itoa(voucher.Decimals),
})
}
return swapToList, nil
}
func (das *DevAccountService) GetSwapFromTokenMaxLimit(ctx context.Context, poolAddress, fromTokenAddress, toTokenAddress, publicKey string) (*models.MaxLimitResult, error) {
p, ok := das.pools[poolAddress]
if !ok {
return nil, fmt.Errorf("Pool address: %v not found ", poolAddress)
}
limit, ok := p.PoolLimit[fromTokenAddress]
if !ok {
return nil, fmt.Errorf("Token address: %v not found in the pool", fromTokenAddress)
}
return &models.MaxLimitResult{
Max: limit,
}, nil
}
func (das *DevAccountService) CheckTokenInPool(ctx context.Context, poolAddress, tokenAddress string) (*models.TokenInPoolResult, error) {
return &models.TokenInPoolResult{
CanSwapFrom: true,
}, nil
}

32
dev/data/swap_from.json Normal file
View File

@ -0,0 +1,32 @@
{
"ok": true,
"description": "Swap from list",
"result": {
"filtered": [
{
"contractAddress": "0xc7B78Ac9ACB9E025C8234621FC515bC58179dEAe",
"tokenSymbol": "AMANI",
"tokenDecimals": "6",
"balance": ""
},
{
"contractAddress": "0xF0C3C7581b8b96B59a97daEc8Bd48247cE078674",
"tokenSymbol": "AMUA",
"tokenDecimals": "6",
"balance": ""
},
{
"contractAddress": "0x371455a30fc62736145Bd8429Fcc6481186f235F",
"tokenSymbol": "BAHARI",
"tokenDecimals": "6",
"balance": ""
},
{
"contractAddress": "0x7cA6113b59c24a880F382C7E12d609a6Eb05246b",
"tokenSymbol": "BANGLA",
"tokenDecimals": "6",
"balance": ""
}
]
}
}

14
dev/data/swap_to.json Normal file
View File

@ -0,0 +1,14 @@
{
"ok": true,
"description": "Swap to list",
"result": {
"filtered": [
{
"contractAddress": "0x765DE816845861e75A25fCA122bb6898B8B1282a",
"tokenSymbol": "cUSD",
"tokenDecimals": "18",
"balance": ""
}
]
}
}

43
dev/data/top_pools.json Normal file
View File

@ -0,0 +1,43 @@
{
"ok": true,
"description": "Top 5 pools sorted by swaps",
"result": {
"topPools": [
{
"poolName": "Kenya ROLA Pool",
"poolSymbol": "ROLA",
"poolContractAddress": "0x48a953cA5cf5298bc6f6Af3C608351f537AAcb9e",
"limiterAddress": "",
"voucherRegistry": ""
},
{
"poolName": "Nairobi ROLA Pool",
"poolSymbol": "NAIROBI",
"poolContractAddress": "0xB0660Ac1Ee3d32ea35bc728D7CA1705Fa5A37528",
"limiterAddress": "",
"voucherRegistry": ""
},
{
"poolName": "Friends of Kiriba Ecosystem ",
"poolSymbol": "FRIENDS",
"poolContractAddress": "0xC4848263821FA02baB2181910A2eFb9CECb2c21C",
"limiterAddress": "",
"voucherRegistry": ""
},
{
"poolName": "Resilient Community Waqfs",
"poolSymbol": "REZILIENS",
"poolContractAddress": "0x1e40951d7a28147D8B4A554C60c42766C92e2Fc6",
"limiterAddress": "",
"voucherRegistry": ""
},
{
"poolName": "GrE Tech",
"poolSymbol": "GRET",
"poolContractAddress": "0xb7B9d0A264eD1a8E2418571B7AC5933C79C9c2B8",
"limiterAddress": "",
"voucherRegistry": ""
}
]
}
}

2
go.mod
View File

@ -8,7 +8,7 @@ require (
git.grassecon.net/grassrootseconomics/visedriver v0.8.0-beta.10.0.20250122123424-6749c632b0a2 git.grassecon.net/grassrootseconomics/visedriver v0.8.0-beta.10.0.20250122123424-6749c632b0a2
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.2.0-beta github.com/grassrootseconomics/ussd-data-service v1.6.0-beta
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
) )

10
go.sum
View File

@ -18,6 +18,16 @@ github.com/grassrootseconomics/eth-custodial v1.3.0-beta h1:twrMBhl89GqDUL9PlkzQ
github.com/grassrootseconomics/eth-custodial v1.3.0-beta/go.mod h1:7uhRcdnJplX4t6GKCEFkbeDhhjlcaGJeJqevbcvGLZo= github.com/grassrootseconomics/eth-custodial v1.3.0-beta/go.mod h1:7uhRcdnJplX4t6GKCEFkbeDhhjlcaGJeJqevbcvGLZo=
github.com/grassrootseconomics/ussd-data-service v1.2.0-beta h1:fn1gwbWIwHVEBtUC2zi5OqTlfI/5gU1SMk0fgGixIXk= github.com/grassrootseconomics/ussd-data-service v1.2.0-beta h1:fn1gwbWIwHVEBtUC2zi5OqTlfI/5gU1SMk0fgGixIXk=
github.com/grassrootseconomics/ussd-data-service v1.2.0-beta/go.mod h1:omfI0QtUwIdpu9gMcUqLMCG8O1XWjqJGBx1qUMiGWC0= github.com/grassrootseconomics/ussd-data-service v1.2.0-beta/go.mod h1:omfI0QtUwIdpu9gMcUqLMCG8O1XWjqJGBx1qUMiGWC0=
github.com/grassrootseconomics/ussd-data-service v1.4.0-beta h1:4fMd/3h2ZIhRg4GdHQmRw5FfD3MpJvFNNJQo+Q27f5M=
github.com/grassrootseconomics/ussd-data-service v1.4.0-beta/go.mod h1:9sGnorpKaK76FmOGXoh/xv7x5siSFNYdXxQo9BKW4DI=
github.com/grassrootseconomics/ussd-data-service v1.4.4-beta h1:turlyo0i3OLj29mWpWNoB/3Qao8qEngT/5d1jDWTZE4=
github.com/grassrootseconomics/ussd-data-service v1.4.4-beta/go.mod h1:9sGnorpKaK76FmOGXoh/xv7x5siSFNYdXxQo9BKW4DI=
github.com/grassrootseconomics/ussd-data-service v1.4.7-beta h1:yAe1YaOBsdxW2m20jnVU4F0kLmFr+mK/gHCWEdHmE90=
github.com/grassrootseconomics/ussd-data-service v1.4.7-beta/go.mod h1:9sGnorpKaK76FmOGXoh/xv7x5siSFNYdXxQo9BKW4DI=
github.com/grassrootseconomics/ussd-data-service v1.5.0-beta h1:BSSQL/yPEvTVku9ja/ENZyZdwZkEaiTzzOUfg72bTy4=
github.com/grassrootseconomics/ussd-data-service v1.5.0-beta/go.mod h1:9sGnorpKaK76FmOGXoh/xv7x5siSFNYdXxQo9BKW4DI=
github.com/grassrootseconomics/ussd-data-service v1.6.0-beta h1:pY6zns6ifXyClRxP+JJaWrged3oRE7tTS2xaftF9clA=
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 h1:U4kkNYryi/qfbBF8gh7Vsbuz+cVmhf5kt6pE9bYYyLo=
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4/go.mod h1:zpZDgZFzeq9s0MIeB1P50NIEWDFFHSFBohI/NbaTD/Y= github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4/go.mod h1:zpZDgZFzeq9s0MIeB1P50NIEWDFFHSFBohI/NbaTD/Y=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=

View File

@ -0,0 +1,22 @@
package models
type PoolDepositResult struct {
TrackingId string `json:"trackingId"`
}
type PoolSwapQuoteResult struct {
IncludesFeesDeduction bool `json:"includesFeesDeduction"`
OutValue string `json:"outValue"`
}
type PoolSwapResult struct {
TrackingId string `json:"trackingId"`
}
type MaxLimitResult struct {
Max string `json:"max"`
}
type TokenInPoolResult struct {
CanSwapFrom bool `json:"canSwapFrom"`
}

View File

@ -20,4 +20,13 @@ type AccountService interface {
SendUpsellSMS(ctx context.Context, inviterPhone, inviteePhone string) (*models.SendSMSResponse, error) SendUpsellSMS(ctx context.Context, inviterPhone, inviteePhone string) (*models.SendSMSResponse, error)
SendAddressSMS(ctx context.Context, publicKey, originPhone string) error SendAddressSMS(ctx context.Context, publicKey, originPhone string) error
SendPINResetSMS(ctx context.Context, admin, phone string) error SendPINResetSMS(ctx context.Context, admin, phone string) error
PoolDeposit(ctx context.Context, amount, from, poolAddress, tokenAddress string) (*models.PoolDepositResult, 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)
GetPoolSwappableVouchers(ctx context.Context, poolAddress string) ([]dataserviceapi.TokenHoldings, 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)
GetSwapFromTokenMaxLimit(ctx context.Context, poolAddress, fromTokenAddress, toTokenAddress, publicKey string) (*models.MaxLimitResult, error)
CheckTokenInPool(ctx context.Context, poolAddress, tokenAddress string) (*models.TokenInPoolResult, error)
} }

View File

@ -234,35 +234,283 @@ 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) {
svc := dev.NewDevAccountService(ctx, as.SS)
if as.UseApi {
return fetchCustodialTopPools(ctx)
} else {
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) {
var r models.PoolDepositResult
//pool deposit payload
payload := map[string]string{
"amount": amount,
"from": from,
"poolAddress": poolAddress,
"tokenAddress": tokenAddress,
}
payloadBytes, err := json.Marshal(payload)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", config.TokenTransferURL, 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) GetPoolSwapQuote(ctx context.Context, amount, from, fromTokenAddress, poolAddress, toTokenAddress string) (*models.PoolSwapQuoteResult, error) {
var r models.PoolSwapQuoteResult
//pool swap quote payload
payload := map[string]string{
"amount": amount,
"from": from,
"fromTokenAddress": fromTokenAddress,
"poolAddress": poolAddress,
"toTokenAddress": toTokenAddress,
}
payloadBytes, err := json.Marshal(payload)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", config.PoolSwapQuoteURL, 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) 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)
return svc.GetPoolSwappableFromVouchers(ctx, poolAddress, publicKey)
}
}
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)
return r.PoolSwappableVouchers, nil
}
func (as *HTTPAccountService) GetPoolSwappableVouchers(ctx context.Context, poolAddress string) ([]dataserviceapi.TokenHoldings, error) {
svc := dev.NewDevAccountService(ctx, as.SS)
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)
return r.PoolSwappableVouchers, nil
}
func (as *HTTPAccountService) PoolSwap(ctx context.Context, amount, from, fromTokenAddress, poolAddress, toTokenAddress string) (*models.PoolSwapResult, error) {
var r models.PoolSwapResult
//swap payload
payload := map[string]string{
"amount": amount,
"from": from,
"fromTokenAddress": fromTokenAddress,
"poolAddress": poolAddress,
"toTokenAddress": toTokenAddress,
}
payloadBytes, err := json.Marshal(payload)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", config.PoolSwapURL, 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) 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)
return svc.GetSwapFromTokenMaxLimit(ctx, poolAddress, fromTokenAddress, toTokenAddress, publicKey)
}
}
func (as *HTTPAccountService) getSwapFromTokenMaxLimit(ctx context.Context, poolAddress, fromTokenAddress, toTokeAddress, publicKey string) (*models.MaxLimitResult, error) {
var r models.MaxLimitResult
ep, err := url.JoinPath(config.PoolSwappableVouchersURL, poolAddress, "limit", fromTokenAddress, toTokeAddress, 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")
@ -285,11 +533,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,
@ -299,7 +545,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
} }
@ -402,6 +648,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)
@ -412,22 +659,26 @@ 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, errors.New(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")
} }
@ -444,16 +695,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

@ -70,3 +70,48 @@ func (m *MockAccountService) SendPINResetSMS(ctx context.Context, admin, phone s
func (m *MockAccountService) SendAddressSMS(ctx context.Context, publicKey, originPhone string) error { func (m *MockAccountService) SendAddressSMS(ctx context.Context, publicKey, originPhone string) error {
return nil return nil
} }
func (m MockAccountService) PoolDeposit(ctx context.Context, amount, from, poolAddress, tokenAddress string) (*models.PoolDepositResult, error) {
args := m.Called(amount, from, poolAddress, tokenAddress)
return args.Get(0).(*models.PoolDepositResult), args.Error(1)
}
func (m MockAccountService) FetchTopPools(ctx context.Context) ([]dataserviceapi.PoolDetails, error) {
args := m.Called()
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) {
args := m.Called(poolAddress, publicKey)
return args.Get(0).([]dataserviceapi.TokenHoldings), args.Error(1)
}
func (m MockAccountService) GetPoolSwappableVouchers(ctx context.Context, poolAddress string) ([]dataserviceapi.TokenHoldings, error) {
args := m.Called(poolAddress)
return args.Get(0).([]dataserviceapi.TokenHoldings), args.Error(1)
}
func (m MockAccountService) GetPoolSwapQuote(ctx context.Context, amount, from, fromTokenAddress, poolAddress, toTokenAddress string) (*models.PoolSwapQuoteResult, error) {
args := m.Called(amount, from, fromTokenAddress, poolAddress, toTokenAddress)
return args.Get(0).(*models.PoolSwapQuoteResult), args.Error(1)
}
func (m MockAccountService) PoolSwap(ctx context.Context, amount, from, fromTokenAddress, poolAddress, toTokenAddress string) (*models.PoolSwapResult, error) {
args := m.Called(amount, from, fromTokenAddress, poolAddress, toTokenAddress)
return args.Get(0).(*models.PoolSwapResult), args.Error(1)
}
func (m MockAccountService) GetSwapFromTokenMaxLimit(ctx context.Context, poolAddress, fromTokenAddress, toTokenAddress, publicKey string) (*models.MaxLimitResult, error) {
args := m.Called(poolAddress, fromTokenAddress, toTokenAddress, publicKey)
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)
}

View File

@ -35,10 +35,10 @@ 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{ dataserviceapi.TokenHoldings{
ContractAddress: "0x6CC75A06ac72eB4Db2eE22F781F5D100d8ec03ee", TokenAddress: "0x6CC75A06ac72eB4Db2eE22F781F5D100d8ec03ee",
TokenSymbol: "SRF", TokenSymbol: "SRF",
TokenDecimals: "6", TokenDecimals: "6",
Balance: "2745987", Balance: "2745987",
}, },
}, nil }, nil
} }
@ -57,6 +57,10 @@ func (tas *TestAccountService) TokenTransfer(ctx context.Context, amount, from,
}, nil }, nil
} }
func (m TestAccountService) PoolDeposit(ctx context.Context, amount, from, poolAddress, tokenAddress string) (*models.PoolDepositResult, error) {
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
} }
@ -76,3 +80,35 @@ func (m *TestAccountService) SendAddressSMS(ctx context.Context, publicKey, orig
func (m *TestAccountService) SendPINResetSMS(ctx context.Context, admin, phone string) error { func (m *TestAccountService) SendPINResetSMS(ctx context.Context, admin, phone string) error {
return nil return nil
} }
func (m TestAccountService) FetchTopPools(ctx context.Context) ([]dataserviceapi.PoolDetails, error) {
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) {
return []dataserviceapi.TokenHoldings{}, nil
}
func (m TestAccountService) GetPoolSwappableVouchers(ctx context.Context, poolAddress string) ([]dataserviceapi.TokenHoldings, error) {
return []dataserviceapi.TokenHoldings{}, nil
}
func (m TestAccountService) GetPoolSwapQuote(ctx context.Context, amount, from, fromTokenAddress, poolAddress, toTokenAddress string) (*models.PoolSwapQuoteResult, error) {
return &models.PoolSwapQuoteResult{}, nil
}
func (m TestAccountService) PoolSwap(ctx context.Context, amount, from, fromTokenAddress, poolAddress, toTokenAddress string) (*models.PoolSwapResult, error) {
return &models.PoolSwapResult{}, nil
}
func (m TestAccountService) GetSwapFromTokenMaxLimit(ctx context.Context, poolAddress, fromTokenAddress, toTokenAddress, publicKey string) (*models.MaxLimitResult, error) {
return &models.MaxLimitResult{}, nil
}
func (m TestAccountService) CheckTokenInPool(ctx context.Context, poolAddress, tokenAddress string) (*models.TokenInPoolResult, error) {
return &models.TokenInPoolResult{}, nil
}