github.com/ethersphere/bee/v2@v2.2.0/pkg/api/wallet.go (about)

     1  // Copyright 2022 The Swarm Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package api
     6  
     7  import (
     8  	"math/big"
     9  	"net/http"
    10  	"strings"
    11  
    12  	"slices"
    13  
    14  	"github.com/ethereum/go-ethereum/common"
    15  	"github.com/ethersphere/bee/v2/pkg/bigint"
    16  	"github.com/ethersphere/bee/v2/pkg/jsonhttp"
    17  	"github.com/ethersphere/bee/v2/pkg/sctx"
    18  	"github.com/ethersphere/bee/v2/pkg/transaction"
    19  	"github.com/gorilla/mux"
    20  )
    21  
    22  type walletResponse struct {
    23  	BZZ                       *bigint.BigInt `json:"bzzBalance"`                // the BZZ balance of the wallet associated with the eth address of the node
    24  	NativeToken               *bigint.BigInt `json:"nativeTokenBalance"`        // the native token balance of the wallet associated with the eth address of the node
    25  	ChainID                   int64          `json:"chainID"`                   // the id of the blockchain
    26  	ChequebookContractAddress common.Address `json:"chequebookContractAddress"` // the address of the chequebook contract
    27  	WalletAddress             common.Address `json:"walletAddress"`             // the address of the bee wallet
    28  }
    29  
    30  func (s *Service) walletHandler(w http.ResponseWriter, r *http.Request) {
    31  	logger := s.logger.WithName("get_wallet").Build()
    32  
    33  	nativeToken, err := s.chainBackend.BalanceAt(r.Context(), s.ethereumAddress, nil)
    34  	if err != nil {
    35  		logger.Debug("unable to acquire balance from the chain backend", "error", err)
    36  		logger.Error(nil, "unable to acquire balance from the chain backend")
    37  		jsonhttp.InternalServerError(w, "unable to acquire balance from the chain backend")
    38  		return
    39  	}
    40  
    41  	bzz, err := s.erc20Service.BalanceOf(r.Context(), s.ethereumAddress)
    42  	if err != nil {
    43  		logger.Debug("unable to acquire erc20 balance", "error", err)
    44  		logger.Error(nil, "unable to acquire erc20 balance")
    45  		jsonhttp.InternalServerError(w, "unable to acquire erc20 balance")
    46  		return
    47  	}
    48  
    49  	jsonhttp.OK(w, walletResponse{
    50  		BZZ:                       bigint.Wrap(bzz),
    51  		NativeToken:               bigint.Wrap(nativeToken),
    52  		ChainID:                   s.chainID,
    53  		ChequebookContractAddress: s.chequebook.Address(),
    54  		WalletAddress:             s.ethereumAddress,
    55  	})
    56  }
    57  
    58  type walletTxResponse struct {
    59  	TransactionHash common.Hash `json:"transactionHash"`
    60  }
    61  
    62  func (s *Service) walletWithdrawHandler(w http.ResponseWriter, r *http.Request) {
    63  	logger := s.logger.WithName("post_wallet_withdraw").Build()
    64  
    65  	queries := struct {
    66  		Amount  *big.Int        `map:"amount" validate:"required"`
    67  		Address *common.Address `map:"address" validate:"required"`
    68  	}{}
    69  
    70  	if response := s.mapStructure(r.URL.Query(), &queries); response != nil {
    71  		response("invalid query params", logger, w)
    72  		return
    73  	}
    74  
    75  	path := struct {
    76  		Coin *string `map:"coin" validate:"required"`
    77  	}{}
    78  
    79  	if response := s.mapStructure(mux.Vars(r), &path); response != nil {
    80  		response("invalid query params", logger, w)
    81  		return
    82  	}
    83  
    84  	var bzz bool
    85  
    86  	if strings.EqualFold("BZZ", *path.Coin) {
    87  		bzz = true
    88  	} else if !strings.EqualFold("NativeToken", *path.Coin) {
    89  		jsonhttp.BadRequest(w, "only BZZ or NativeToken options are accepted")
    90  		return
    91  	}
    92  
    93  	if !slices.Contains(s.whitelistedWithdrawalAddress, *queries.Address) {
    94  		jsonhttp.BadRequest(w, "provided address not whitelisted")
    95  		return
    96  	}
    97  
    98  	if bzz {
    99  		currentBalance, err := s.erc20Service.BalanceOf(r.Context(), s.ethereumAddress)
   100  		if err != nil {
   101  			logger.Error(err, "unable to get balance")
   102  			jsonhttp.InternalServerError(w, "unable to get balance")
   103  			return
   104  		}
   105  
   106  		if queries.Amount.Cmp(currentBalance) > 0 {
   107  			logger.Error(err, "not enough balance")
   108  			jsonhttp.BadRequest(w, "not enough balance")
   109  			return
   110  		}
   111  
   112  		txHash, err := s.erc20Service.Transfer(r.Context(), *queries.Address, queries.Amount)
   113  		if err != nil {
   114  			logger.Error(err, "unable to transfer")
   115  			jsonhttp.InternalServerError(w, "unable to transfer amount")
   116  			return
   117  		}
   118  		jsonhttp.OK(w, walletTxResponse{TransactionHash: txHash})
   119  		return
   120  	}
   121  
   122  	nativeToken, err := s.chainBackend.BalanceAt(r.Context(), s.ethereumAddress, nil)
   123  	if err != nil {
   124  		logger.Error(err, "unable to acquire balance from the chain backend")
   125  		jsonhttp.InternalServerError(w, "unable to acquire balance from the chain backend")
   126  		return
   127  	}
   128  
   129  	if queries.Amount.Cmp(nativeToken) > 0 {
   130  		jsonhttp.BadRequest(w, "not enough balance")
   131  		return
   132  	}
   133  
   134  	req := &transaction.TxRequest{
   135  		To:          queries.Address,
   136  		GasPrice:    sctx.GetGasPrice(r.Context()),
   137  		GasLimit:    sctx.GetGasLimitWithDefault(r.Context(), 300_000),
   138  		Value:       queries.Amount,
   139  		Description: "native token withdraw",
   140  	}
   141  
   142  	txHash, err := s.transaction.Send(r.Context(), req, transaction.DefaultTipBoostPercent)
   143  	if err != nil {
   144  		logger.Error(err, "unable to transfer")
   145  		jsonhttp.InternalServerError(w, "unable to transfer")
   146  		return
   147  	}
   148  
   149  	jsonhttp.OK(w, walletTxResponse{TransactionHash: txHash})
   150  }