diff --git a/pkg/api/router.go b/pkg/api/router.go index 3e4574ed896..f6f75e348ea 100644 --- a/pkg/api/router.go +++ b/pkg/api/router.go @@ -491,6 +491,12 @@ func (s *Service) mountBusinessDebug(restricted bool) { handle("/wallet", jsonhttp.MethodHandler{ "GET": http.HandlerFunc(s.walletHandler), }) + handle("/wallet/withdraw/{coin}/{addr}", jsonhttp.MethodHandler{ + "POST": web.ChainHandlers( + s.gasConfigMiddleware("wallet withdraw"), + web.FinalHandlerFunc(s.walletWithdrawHandler), + ), + }) } } diff --git a/pkg/api/wallet.go b/pkg/api/wallet.go index c98e83b5987..93f184c3eaa 100644 --- a/pkg/api/wallet.go +++ b/pkg/api/wallet.go @@ -5,11 +5,15 @@ package api import ( + "context" + "math/big" "net/http" "github.com/ethereum/go-ethereum/common" "github.com/ethersphere/bee/pkg/bigint" "github.com/ethersphere/bee/pkg/jsonhttp" + "github.com/ethersphere/bee/pkg/transaction" + "github.com/gorilla/mux" ) type walletResponse struct { @@ -20,6 +24,10 @@ type walletResponse struct { WalletAddress common.Address `json:"walletAddress"` // the address of the bee wallet } +type walletTxResponse struct { + TransactionHash common.Hash `json:"transactionHash"` +} + func (s *Service) walletHandler(w http.ResponseWriter, r *http.Request) { logger := s.logger.WithName("get_wallet").Build() @@ -47,3 +55,82 @@ func (s *Service) walletHandler(w http.ResponseWriter, r *http.Request) { WalletAddress: s.ethereumAddress, }) } + +func (s *Service) walletWithdrawHandler(w http.ResponseWriter, r *http.Request) { + logger := s.logger.WithName("post_wallet_withdraw").Build() + + queries := struct { + Amount *big.Int `map:"amount" validate:"required"` + }{} + + if response := s.mapStructure(r.URL.Query(), &queries); response != nil { + response("invalid query params", logger, w) + return + } + + path := struct { + Coin *string `map:"coin" validate:"required"` + Address *common.Address `map:"address" validate:"required"` + }{} + + if response := s.mapStructure(mux.Vars(r), &path); response != nil { + response("invalid query params", logger, w) + return + } + + ctx := r.Context() + var bzz bool + // check if coin is xdai or bzz + // check whitelisted + + if bzz { + currentBalance, err := s.erc20Service.BalanceOf(ctx, s.ethereumAddress) + if err != nil { + logger.Error(err, "unable to get balance") + jsonhttp.InternalServerError(w, "unable to get balance") + return + } + + if queries.Amount.Cmp(currentBalance) > 0 { + logger.Error(err, "not enough balance") + jsonhttp.InternalServerError(w, "not enough balance") + return + } + + txHash, err := s.erc20Service.Withdraw(ctx, *path.Address, queries.Amount) + if err != nil { + logger.Error(err, "unable to transfer") + jsonhttp.InternalServerError(w, "unable to transfer amount") + return + } + jsonhttp.OK(w, walletTxResponse{TransactionHash: txHash}) + return + } + + nativeToken, err := s.chainBackend.BalanceAt(r.Context(), s.ethereumAddress, nil) + if err != nil { + logger.Debug("unable to acquire balance from the chain backend", "error", err) + logger.Error(nil, "unable to acquire balance from the chain backend") + jsonhttp.InternalServerError(w, "unable to acquire balance from the chain backend") + return + } + + if queries.Amount.Cmp(nativeToken) > 0 { + logger.Error(err, "not enough balance") + jsonhttp.InternalServerError(w, "not enough balance") + return + } + + txHash, err := withdraw(ctx, s.chainBackend, *path.Address, queries.Amount) + if err != nil { + logger.Error(err, "withdraw") + jsonhttp.InternalServerError(w, "pending nonce") + return + } + + jsonhttp.OK(w, walletTxResponse{TransactionHash: txHash}) +} + +func withdraw(context.Context, transaction.Backend, common.Address, *big.Int) (common.Hash, error) { + return common.Hash{}, nil +} diff --git a/pkg/settlement/swap/erc20/erc20.go b/pkg/settlement/swap/erc20/erc20.go index 510cc43769b..1fe5bb515c1 100644 --- a/pkg/settlement/swap/erc20/erc20.go +++ b/pkg/settlement/swap/erc20/erc20.go @@ -25,6 +25,7 @@ var ( type Service interface { BalanceOf(ctx context.Context, address common.Address) (*big.Int, error) Transfer(ctx context.Context, address common.Address, value *big.Int) (common.Hash, error) + Withdraw(ctx context.Context, address common.Address, value *big.Int) (common.Hash, error) } type erc20Service struct { @@ -91,3 +92,26 @@ func (c *erc20Service) Transfer(ctx context.Context, address common.Address, val return txHash, nil } + +func (c *erc20Service) Withdraw(ctx context.Context, address common.Address, value *big.Int) (common.Hash, error) { + callData, err := erc20ABI.Pack("transfer", c.address, value) + if err != nil { + return common.Hash{}, err + } + + request := &transaction.TxRequest{ + To: &address, + Data: callData, + GasPrice: sctx.GetGasPrice(ctx), + GasLimit: 90000, + Value: big.NewInt(0), + Description: "token transfer", + } + + txHash, err := c.transactionService.Send(ctx, request, transaction.DefaultTipBoostPercent) + if err != nil { + return common.Hash{}, err + } + + return txHash, nil +} diff --git a/pkg/settlement/swap/erc20/mock/erc20.go b/pkg/settlement/swap/erc20/mock/erc20.go index 8d7652d2fb3..15da78c6d82 100644 --- a/pkg/settlement/swap/erc20/mock/erc20.go +++ b/pkg/settlement/swap/erc20/mock/erc20.go @@ -16,6 +16,7 @@ import ( type Service struct { balanceOfFunc func(ctx context.Context, address common.Address) (*big.Int, error) transferFunc func(ctx context.Context, address common.Address, value *big.Int) (common.Hash, error) + withdrawFunc func(ctx context.Context, address common.Address, value *big.Int) (common.Hash, error) } func WithBalanceOfFunc(f func(ctx context.Context, address common.Address) (*big.Int, error)) Option { @@ -52,6 +53,13 @@ func (s *Service) Transfer(ctx context.Context, address common.Address, value *b return common.Hash{}, errors.New("Error") } +func (s *Service) Withdraw(ctx context.Context, address common.Address, value *big.Int) (common.Hash, error) { + if s.transferFunc != nil { + return s.withdrawFunc(ctx, address, value) + } + return common.Hash{}, errors.New("Error") +} + // Option is the option passed to the mock Chequebook service type Option interface { apply(*Service)