restucturization of recipients payment methods
All checks were successful
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/chain_gateway Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/nats Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful
All checks were successful
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/chain_gateway Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/nats Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful
This commit is contained in:
557
api/gateway/chain/internal/service/gateway/service_test.go
Normal file
557
api/gateway/chain/internal/service/gateway/service_test.go
Normal file
@@ -0,0 +1,557 @@
|
||||
package gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
ichainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.uber.org/zap"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/tech/sendico/gateway/chain/internal/keymanager"
|
||||
"github.com/tech/sendico/gateway/chain/internal/service/gateway/shared"
|
||||
"github.com/tech/sendico/gateway/chain/storage"
|
||||
"github.com/tech/sendico/gateway/chain/storage/model"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||
paginationv1 "github.com/tech/sendico/pkg/proto/common/pagination/v1"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
const (
|
||||
walletDefaultLimit int64 = 50
|
||||
walletMaxLimit int64 = 200
|
||||
transferDefaultLimit int64 = 50
|
||||
transferMaxLimit int64 = 200
|
||||
depositDefaultLimit int64 = 100
|
||||
depositMaxLimit int64 = 500
|
||||
)
|
||||
|
||||
func TestCreateManagedWallet_Idempotent(t *testing.T) {
|
||||
svc, repo := newTestService(t)
|
||||
|
||||
ctx := context.Background()
|
||||
req := &ichainv1.CreateManagedWalletRequest{
|
||||
IdempotencyKey: "idem-1",
|
||||
OrganizationRef: "org-1",
|
||||
OwnerRef: "owner-1",
|
||||
Asset: &ichainv1.Asset{
|
||||
Chain: ichainv1.ChainNetwork_CHAIN_NETWORK_ETHEREUM_MAINNET,
|
||||
TokenSymbol: "USDC",
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := svc.CreateManagedWallet(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp.GetWallet())
|
||||
firstRef := resp.GetWallet().GetWalletRef()
|
||||
require.NotEmpty(t, firstRef)
|
||||
|
||||
resp2, err := svc.CreateManagedWallet(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, firstRef, resp2.GetWallet().GetWalletRef())
|
||||
|
||||
// ensure stored only once
|
||||
require.Equal(t, 1, repo.wallets.count())
|
||||
}
|
||||
|
||||
func TestSubmitTransfer_ManagedDestination(t *testing.T) {
|
||||
svc, repo := newTestService(t)
|
||||
ctx := context.Background()
|
||||
|
||||
// create source wallet
|
||||
srcResp, err := svc.CreateManagedWallet(ctx, &ichainv1.CreateManagedWalletRequest{
|
||||
IdempotencyKey: "idem-src",
|
||||
OrganizationRef: "org-1",
|
||||
OwnerRef: "owner-1",
|
||||
Asset: &ichainv1.Asset{
|
||||
Chain: ichainv1.ChainNetwork_CHAIN_NETWORK_ETHEREUM_MAINNET,
|
||||
TokenSymbol: "USDC",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
srcRef := srcResp.GetWallet().GetWalletRef()
|
||||
|
||||
// destination wallet
|
||||
dstResp, err := svc.CreateManagedWallet(ctx, &ichainv1.CreateManagedWalletRequest{
|
||||
IdempotencyKey: "idem-dst",
|
||||
OrganizationRef: "org-1",
|
||||
OwnerRef: "owner-2",
|
||||
Asset: &ichainv1.Asset{
|
||||
Chain: ichainv1.ChainNetwork_CHAIN_NETWORK_ETHEREUM_MAINNET,
|
||||
TokenSymbol: "USDC",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
dstRef := dstResp.GetWallet().GetWalletRef()
|
||||
|
||||
transferResp, err := svc.SubmitTransfer(ctx, &ichainv1.SubmitTransferRequest{
|
||||
IdempotencyKey: "transfer-1",
|
||||
OrganizationRef: "org-1",
|
||||
SourceWalletRef: srcRef,
|
||||
Destination: &ichainv1.TransferDestination{
|
||||
Destination: &ichainv1.TransferDestination_ManagedWalletRef{ManagedWalletRef: dstRef},
|
||||
},
|
||||
Amount: &moneyv1.Money{Currency: "USDC", Amount: "100"},
|
||||
Fees: []*ichainv1.ServiceFeeBreakdown{
|
||||
{
|
||||
FeeCode: "service",
|
||||
Amount: &moneyv1.Money{Currency: "USDC", Amount: "5"},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, transferResp.GetTransfer())
|
||||
require.Equal(t, "95", transferResp.GetTransfer().GetNetAmount().GetAmount())
|
||||
|
||||
stored := repo.transfers.get(transferResp.GetTransfer().GetTransferRef())
|
||||
require.NotNil(t, stored)
|
||||
require.Equal(t, model.TransferStatusPending, stored.Status)
|
||||
|
||||
// GetTransfer
|
||||
getResp, err := svc.GetTransfer(ctx, &ichainv1.GetTransferRequest{TransferRef: stored.TransferRef})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, stored.TransferRef, getResp.GetTransfer().GetTransferRef())
|
||||
|
||||
// ListTransfers
|
||||
listResp, err := svc.ListTransfers(ctx, &ichainv1.ListTransfersRequest{
|
||||
SourceWalletRef: srcRef,
|
||||
Page: &paginationv1.CursorPageRequest{Limit: 10},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, listResp.GetTransfers(), 1)
|
||||
require.Equal(t, stored.TransferRef, listResp.GetTransfers()[0].GetTransferRef())
|
||||
}
|
||||
|
||||
func TestGetWalletBalance_NotFound(t *testing.T) {
|
||||
svc, _ := newTestService(t)
|
||||
ctx := context.Background()
|
||||
|
||||
_, err := svc.GetWalletBalance(ctx, &ichainv1.GetWalletBalanceRequest{WalletRef: "missing"})
|
||||
require.Error(t, err)
|
||||
st, _ := status.FromError(err)
|
||||
require.Equal(t, codes.NotFound, st.Code())
|
||||
}
|
||||
|
||||
// ---- in-memory storage implementation ----
|
||||
|
||||
type inMemoryRepository struct {
|
||||
wallets *inMemoryWallets
|
||||
transfers *inMemoryTransfers
|
||||
deposits *inMemoryDeposits
|
||||
}
|
||||
|
||||
func newInMemoryRepository() *inMemoryRepository {
|
||||
return &inMemoryRepository{
|
||||
wallets: newInMemoryWallets(),
|
||||
transfers: newInMemoryTransfers(),
|
||||
deposits: newInMemoryDeposits(),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *inMemoryRepository) Ping(context.Context) error { return nil }
|
||||
func (r *inMemoryRepository) Wallets() storage.WalletsStore { return r.wallets }
|
||||
func (r *inMemoryRepository) Transfers() storage.TransfersStore { return r.transfers }
|
||||
func (r *inMemoryRepository) Deposits() storage.DepositsStore { return r.deposits }
|
||||
|
||||
// Wallets store
|
||||
|
||||
type inMemoryWallets struct {
|
||||
mu sync.Mutex
|
||||
wallets map[string]*model.ManagedWallet
|
||||
byIdemp map[string]string
|
||||
balances map[string]*model.WalletBalance
|
||||
}
|
||||
|
||||
func newInMemoryWallets() *inMemoryWallets {
|
||||
return &inMemoryWallets{
|
||||
wallets: make(map[string]*model.ManagedWallet),
|
||||
byIdemp: make(map[string]string),
|
||||
balances: make(map[string]*model.WalletBalance),
|
||||
}
|
||||
}
|
||||
|
||||
func (w *inMemoryWallets) count() int {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
return len(w.wallets)
|
||||
}
|
||||
|
||||
func (w *inMemoryWallets) Create(ctx context.Context, wallet *model.ManagedWallet) (*model.ManagedWallet, error) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
if wallet == nil {
|
||||
return nil, merrors.InvalidArgument("walletsStore: nil wallet")
|
||||
}
|
||||
wallet.Normalize()
|
||||
if wallet.IdempotencyKey == "" {
|
||||
return nil, merrors.InvalidArgument("walletsStore: empty idempotencyKey")
|
||||
}
|
||||
|
||||
if existingRef, ok := w.byIdemp[wallet.IdempotencyKey]; ok {
|
||||
existing := w.wallets[existingRef]
|
||||
return existing, merrors.ErrDataConflict
|
||||
}
|
||||
|
||||
if wallet.WalletRef == "" {
|
||||
wallet.WalletRef = primitive.NewObjectID().Hex()
|
||||
}
|
||||
if wallet.GetID() == nil || wallet.GetID().IsZero() {
|
||||
wallet.SetID(primitive.NewObjectID())
|
||||
} else {
|
||||
wallet.Update()
|
||||
}
|
||||
|
||||
w.wallets[wallet.WalletRef] = wallet
|
||||
w.byIdemp[wallet.IdempotencyKey] = wallet.WalletRef
|
||||
return wallet, nil
|
||||
}
|
||||
|
||||
func (w *inMemoryWallets) Get(ctx context.Context, walletRef string) (*model.ManagedWallet, error) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
wallet, ok := w.wallets[strings.TrimSpace(walletRef)]
|
||||
if !ok {
|
||||
return nil, merrors.NoData("wallet not found")
|
||||
}
|
||||
return wallet, nil
|
||||
}
|
||||
|
||||
func (w *inMemoryWallets) List(ctx context.Context, filter model.ManagedWalletFilter) (*model.ManagedWalletList, error) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
items := make([]*model.ManagedWallet, 0, len(w.wallets))
|
||||
for _, wallet := range w.wallets {
|
||||
if filter.OrganizationRef != "" && !strings.EqualFold(wallet.OrganizationRef, filter.OrganizationRef) {
|
||||
continue
|
||||
}
|
||||
if filter.OwnerRef != "" && !strings.EqualFold(wallet.OwnerRef, filter.OwnerRef) {
|
||||
continue
|
||||
}
|
||||
if filter.Network != "" && !strings.EqualFold(wallet.Network, filter.Network) {
|
||||
continue
|
||||
}
|
||||
if filter.TokenSymbol != "" && !strings.EqualFold(wallet.TokenSymbol, filter.TokenSymbol) {
|
||||
continue
|
||||
}
|
||||
items = append(items, wallet)
|
||||
}
|
||||
|
||||
sort.Slice(items, func(i, j int) bool {
|
||||
return items[i].ID.Timestamp().Before(items[j].ID.Timestamp())
|
||||
})
|
||||
|
||||
startIndex := 0
|
||||
if cursor := strings.TrimSpace(filter.Cursor); cursor != "" {
|
||||
if oid, err := primitive.ObjectIDFromHex(cursor); err == nil {
|
||||
for idx, item := range items {
|
||||
if item.ID.Timestamp().After(oid.Timestamp()) {
|
||||
startIndex = idx
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
limit := int(sanitizeLimit(filter.Limit, walletDefaultLimit, walletMaxLimit))
|
||||
end := startIndex + limit
|
||||
hasMore := false
|
||||
if end < len(items) {
|
||||
hasMore = true
|
||||
items = items[startIndex:end]
|
||||
} else {
|
||||
items = items[startIndex:]
|
||||
}
|
||||
|
||||
nextCursor := ""
|
||||
if hasMore && len(items) > 0 {
|
||||
nextCursor = items[len(items)-1].ID.Hex()
|
||||
}
|
||||
|
||||
return &model.ManagedWalletList{Items: items, NextCursor: nextCursor}, nil
|
||||
}
|
||||
|
||||
func (w *inMemoryWallets) SaveBalance(ctx context.Context, balance *model.WalletBalance) error {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
if balance == nil {
|
||||
return merrors.InvalidArgument("walletsStore: nil balance")
|
||||
}
|
||||
balance.Normalize()
|
||||
if balance.WalletRef == "" {
|
||||
return merrors.InvalidArgument("walletsStore: empty walletRef for balance")
|
||||
}
|
||||
if balance.CalculatedAt.IsZero() {
|
||||
balance.CalculatedAt = time.Now().UTC()
|
||||
}
|
||||
existing, ok := w.balances[balance.WalletRef]
|
||||
if !ok {
|
||||
if balance.GetID() == nil || balance.GetID().IsZero() {
|
||||
balance.SetID(primitive.NewObjectID())
|
||||
}
|
||||
w.balances[balance.WalletRef] = balance
|
||||
return nil
|
||||
}
|
||||
existing.Available = balance.Available
|
||||
existing.PendingInbound = balance.PendingInbound
|
||||
existing.PendingOutbound = balance.PendingOutbound
|
||||
existing.CalculatedAt = balance.CalculatedAt
|
||||
existing.Update()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *inMemoryWallets) GetBalance(ctx context.Context, walletRef string) (*model.WalletBalance, error) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
balance, ok := w.balances[strings.TrimSpace(walletRef)]
|
||||
if !ok {
|
||||
return nil, merrors.NoData("wallet balance not found")
|
||||
}
|
||||
return balance, nil
|
||||
}
|
||||
|
||||
// Transfers store
|
||||
|
||||
type inMemoryTransfers struct {
|
||||
mu sync.Mutex
|
||||
items map[string]*model.Transfer
|
||||
byIdemp map[string]string
|
||||
}
|
||||
|
||||
func newInMemoryTransfers() *inMemoryTransfers {
|
||||
return &inMemoryTransfers{
|
||||
items: make(map[string]*model.Transfer),
|
||||
byIdemp: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
func (t *inMemoryTransfers) Create(ctx context.Context, transfer *model.Transfer) (*model.Transfer, error) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if transfer == nil {
|
||||
return nil, merrors.InvalidArgument("transfersStore: nil transfer")
|
||||
}
|
||||
transfer.Normalize()
|
||||
if transfer.IdempotencyKey == "" {
|
||||
return nil, merrors.InvalidArgument("transfersStore: empty idempotencyKey")
|
||||
}
|
||||
if ref, ok := t.byIdemp[transfer.IdempotencyKey]; ok {
|
||||
return t.items[ref], merrors.ErrDataConflict
|
||||
}
|
||||
if transfer.TransferRef == "" {
|
||||
transfer.TransferRef = primitive.NewObjectID().Hex()
|
||||
}
|
||||
if transfer.GetID() == nil || transfer.GetID().IsZero() {
|
||||
transfer.SetID(primitive.NewObjectID())
|
||||
} else {
|
||||
transfer.Update()
|
||||
}
|
||||
t.items[transfer.TransferRef] = transfer
|
||||
t.byIdemp[transfer.IdempotencyKey] = transfer.TransferRef
|
||||
return transfer, nil
|
||||
}
|
||||
|
||||
func (t *inMemoryTransfers) Get(ctx context.Context, transferRef string) (*model.Transfer, error) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
transfer, ok := t.items[strings.TrimSpace(transferRef)]
|
||||
if !ok {
|
||||
return nil, merrors.NoData("transfer not found")
|
||||
}
|
||||
return transfer, nil
|
||||
}
|
||||
|
||||
func (t *inMemoryTransfers) List(ctx context.Context, filter model.TransferFilter) (*model.TransferList, error) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
items := make([]*model.Transfer, 0, len(t.items))
|
||||
for _, transfer := range t.items {
|
||||
if filter.SourceWalletRef != "" && !strings.EqualFold(transfer.SourceWalletRef, filter.SourceWalletRef) {
|
||||
continue
|
||||
}
|
||||
if filter.DestinationWalletRef != "" && !strings.EqualFold(transfer.Destination.ManagedWalletRef, filter.DestinationWalletRef) {
|
||||
continue
|
||||
}
|
||||
if filter.Status != "" && transfer.Status != filter.Status {
|
||||
continue
|
||||
}
|
||||
items = append(items, transfer)
|
||||
}
|
||||
|
||||
sort.Slice(items, func(i, j int) bool {
|
||||
return items[i].ID.Timestamp().Before(items[j].ID.Timestamp())
|
||||
})
|
||||
|
||||
start := 0
|
||||
if cursor := strings.TrimSpace(filter.Cursor); cursor != "" {
|
||||
if oid, err := primitive.ObjectIDFromHex(cursor); err == nil {
|
||||
for idx, item := range items {
|
||||
if item.ID.Timestamp().After(oid.Timestamp()) {
|
||||
start = idx
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
limit := int(sanitizeLimit(filter.Limit, transferDefaultLimit, transferMaxLimit))
|
||||
end := start + limit
|
||||
hasMore := false
|
||||
if end < len(items) {
|
||||
hasMore = true
|
||||
items = items[start:end]
|
||||
} else {
|
||||
items = items[start:]
|
||||
}
|
||||
|
||||
nextCursor := ""
|
||||
if hasMore && len(items) > 0 {
|
||||
nextCursor = items[len(items)-1].ID.Hex()
|
||||
}
|
||||
|
||||
return &model.TransferList{Items: items, NextCursor: nextCursor}, nil
|
||||
}
|
||||
|
||||
func (t *inMemoryTransfers) UpdateStatus(ctx context.Context, transferRef string, status model.TransferStatus, failureReason string, txHash string) (*model.Transfer, error) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
transfer, ok := t.items[strings.TrimSpace(transferRef)]
|
||||
if !ok {
|
||||
return nil, merrors.NoData("transfer not found")
|
||||
}
|
||||
transfer.Status = status
|
||||
if status == model.TransferStatusFailed {
|
||||
transfer.FailureReason = strings.TrimSpace(failureReason)
|
||||
} else {
|
||||
transfer.FailureReason = ""
|
||||
}
|
||||
transfer.TxHash = strings.TrimSpace(txHash)
|
||||
transfer.LastStatusAt = time.Now().UTC()
|
||||
transfer.Update()
|
||||
return transfer, nil
|
||||
}
|
||||
|
||||
// helper for tests
|
||||
func (t *inMemoryTransfers) get(ref string) *model.Transfer {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
return t.items[ref]
|
||||
}
|
||||
|
||||
// Deposits store (minimal for tests)
|
||||
|
||||
type inMemoryDeposits struct {
|
||||
mu sync.Mutex
|
||||
items map[string]*model.Deposit
|
||||
}
|
||||
|
||||
func newInMemoryDeposits() *inMemoryDeposits {
|
||||
return &inMemoryDeposits{items: make(map[string]*model.Deposit)}
|
||||
}
|
||||
|
||||
func (d *inMemoryDeposits) Record(ctx context.Context, deposit *model.Deposit) error {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
if deposit == nil {
|
||||
return merrors.InvalidArgument("depositsStore: nil deposit")
|
||||
}
|
||||
deposit.Normalize()
|
||||
if deposit.DepositRef == "" {
|
||||
return merrors.InvalidArgument("depositsStore: empty depositRef")
|
||||
}
|
||||
if existing, ok := d.items[deposit.DepositRef]; ok {
|
||||
existing.Status = deposit.Status
|
||||
existing.LastStatusAt = time.Now().UTC()
|
||||
existing.Update()
|
||||
return nil
|
||||
}
|
||||
if deposit.GetID() == nil || deposit.GetID().IsZero() {
|
||||
deposit.SetID(primitive.NewObjectID())
|
||||
}
|
||||
if deposit.ObservedAt.IsZero() {
|
||||
deposit.ObservedAt = time.Now().UTC()
|
||||
}
|
||||
if deposit.RecordedAt.IsZero() {
|
||||
deposit.RecordedAt = time.Now().UTC()
|
||||
}
|
||||
deposit.LastStatusAt = time.Now().UTC()
|
||||
d.items[deposit.DepositRef] = deposit
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *inMemoryDeposits) ListPending(ctx context.Context, network string, limit int32) ([]*model.Deposit, error) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
results := make([]*model.Deposit, 0)
|
||||
for _, deposit := range d.items {
|
||||
if deposit.Status != model.DepositStatusPending {
|
||||
continue
|
||||
}
|
||||
if network != "" && !strings.EqualFold(deposit.Network, network) {
|
||||
continue
|
||||
}
|
||||
results = append(results, deposit)
|
||||
}
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].ObservedAt.Before(results[j].ObservedAt)
|
||||
})
|
||||
limitVal := int(sanitizeLimit(limit, depositDefaultLimit, depositMaxLimit))
|
||||
if len(results) > limitVal {
|
||||
results = results[:limitVal]
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// shared helpers
|
||||
|
||||
func sanitizeLimit(requested int32, def, max int64) int64 {
|
||||
if requested <= 0 {
|
||||
return def
|
||||
}
|
||||
if requested > int32(max) {
|
||||
return max
|
||||
}
|
||||
return int64(requested)
|
||||
}
|
||||
|
||||
func newTestService(_ *testing.T) (*Service, *inMemoryRepository) {
|
||||
repo := newInMemoryRepository()
|
||||
logger := zap.NewNop()
|
||||
svc := NewService(logger, repo, nil,
|
||||
WithKeyManager(&fakeKeyManager{}),
|
||||
WithNetworks([]shared.Network{{
|
||||
Name: "ethereum_mainnet",
|
||||
TokenConfigs: []shared.TokenContract{
|
||||
{Symbol: "USDC", ContractAddress: "0xusdc"},
|
||||
},
|
||||
}}),
|
||||
WithServiceWallet(shared.ServiceWallet{Network: "ethereum_mainnet", Address: "0xservice"}),
|
||||
)
|
||||
return svc, repo
|
||||
}
|
||||
|
||||
type fakeKeyManager struct{}
|
||||
|
||||
func (f *fakeKeyManager) CreateManagedWalletKey(ctx context.Context, walletRef string, network string) (*keymanager.ManagedWalletKey, error) {
|
||||
return &keymanager.ManagedWalletKey{
|
||||
KeyID: fmt.Sprintf("%s/%s", strings.ToLower(network), walletRef),
|
||||
Address: "0x" + strings.Repeat("a", 40),
|
||||
PublicKey: strings.Repeat("b", 128),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *fakeKeyManager) SignTransaction(ctx context.Context, keyID string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
||||
return tx, nil
|
||||
}
|
||||
Reference in New Issue
Block a user