github.com/hungdoo/bot@v0.0.0-20240325145135-dd1f386f7b81/src/packages/tombplus/client.go (about)

     1  package tombplus
     2  
     3  import (
     4  	"context"
     5  	"crypto/ecdsa"
     6  	"fmt"
     7  	"math/big"
     8  	"strings"
     9  
    10  	"github.com/ethereum/go-ethereum"
    11  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    12  	"github.com/ethereum/go-ethereum/core/types"
    13  	"github.com/ethereum/go-ethereum/ethclient"
    14  	"github.com/hungdoo/bot/src/common"
    15  	"github.com/hungdoo/bot/src/packages/utils"
    16  	"github.com/shopspring/decimal"
    17  
    18  	ethCommon "github.com/ethereum/go-ethereum/common"
    19  	"github.com/ethereum/go-ethereum/rpc"
    20  )
    21  
    22  type TombplusClient struct {
    23  	ec   *ethclient.Client
    24  	Tomb *Tombplus
    25  }
    26  
    27  var _client *TombplusClient
    28  
    29  func GetClient(rpcEndpoint string, contractAddress ethCommon.Address) (*TombplusClient, error) {
    30  	if _client != nil {
    31  		return _client, nil
    32  	}
    33  	rpcclient, err := rpc.Dial(rpcEndpoint)
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  
    38  	ec := ethclient.NewClient(rpcclient)
    39  	tomeplusContract, err := GetTombplusContract(ec, contractAddress)
    40  	if err != nil {
    41  		return nil, err
    42  	}
    43  	_client = &TombplusClient{
    44  		ec:   ec,
    45  		Tomb: tomeplusContract,
    46  	}
    47  	return _client, nil
    48  }
    49  
    50  func (c *TombplusClient) MaxAllowedFutureFlips() int64 {
    51  	val, err := c.Tomb.MaxAllowedFutureFlips(&bind.CallOpts{})
    52  	if err != nil {
    53  		return -1
    54  	}
    55  	return val.Int64()
    56  }
    57  
    58  func (c *TombplusClient) GameStarted() bool {
    59  	val, err := c.Tomb.GameStarted(&bind.CallOpts{})
    60  	if err != nil {
    61  		return false
    62  	}
    63  	return val
    64  }
    65  
    66  func (c *TombplusClient) GetRewards(user ethCommon.Address) decimal.Decimal {
    67  	reward, err := c.Tomb.RewardBalance(&bind.CallOpts{}, user)
    68  	if err != nil {
    69  		return decimal.Zero
    70  	}
    71  	return utils.DivDecimals(decimal.NewFromBigInt(reward, 0), 18)
    72  }
    73  
    74  func (c *TombplusClient) GetUserLastedVoteEpochId(user ethCommon.Address) (*big.Int, error) {
    75  	flips, err := c.Tomb.GetUserFlips(&bind.CallOpts{}, user)
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  	latestEpoch := big.NewInt(-1)
    80  	for _, v := range flips {
    81  		if v.EpochId.Cmp(latestEpoch) >= 1 {
    82  			latestEpoch = v.EpochId
    83  		}
    84  	}
    85  
    86  	return latestEpoch, nil
    87  }
    88  
    89  func (c *TombplusClient) CanFlipForCurrentEpoch() (bool, error) {
    90  	ok, err := c.Tomb.CanFlipForCurrentEpoch(&bind.CallOpts{})
    91  	if err != nil {
    92  		return false, err
    93  	}
    94  	return ok, nil
    95  }
    96  func (c *TombplusClient) CurrentEpoch() int64 {
    97  	epochNum, err := c.Tomb.CurrentEpochId(&bind.CallOpts{})
    98  	if err != nil {
    99  		return -1
   100  	}
   101  	return epochNum.Int64()
   102  }
   103  
   104  func (c *TombplusClient) GetPauseGameAtEpoch() int64 {
   105  	val, err := c.Tomb.PauseGameAtEpoch(&bind.CallOpts{})
   106  	if err != nil {
   107  		return 0
   108  	}
   109  	return val.Int64()
   110  }
   111  
   112  func (c *TombplusClient) GetUpgradedMasonry() ethCommon.Address {
   113  	val, err := c.Tomb.UpgradedMasonry(&bind.CallOpts{})
   114  	if err != nil {
   115  		return ethCommon.Address{}
   116  	}
   117  	return val
   118  }
   119  
   120  func (c *TombplusClient) IsVotedAtEpoch(user ethCommon.Address, epoch int64) (bool, error) {
   121  	val, err := c.Tomb.GetUserFlipIdByEpochId(&bind.CallOpts{}, user, big.NewInt(epoch))
   122  	if err != nil {
   123  		return false, err
   124  	}
   125  	return val.Found, nil
   126  }
   127  
   128  func (c *TombplusClient) Claim(privateKey *ecdsa.PrivateKey, maxGas *big.Int) (*types.Transaction, *common.ErrorWithSeverity) {
   129  	noSendOpts, err := NewAuthorizedTransactor(c.ec, privateKey, 0, maxGas, big.NewInt(0))
   130  	if err != nil {
   131  		return nil, common.NewErrorWithSeverity(common.Error, err.Error())
   132  	}
   133  
   134  	signedTx, err := c.Tomb.Claim(noSendOpts)
   135  	if err != nil {
   136  		return nil, common.NewErrorWithSeverity(common.Error, err.Error())
   137  	}
   138  
   139  	return c.dryrunAndSend(noSendOpts.From, signedTx)
   140  }
   141  
   142  func (c *TombplusClient) Flip(privateKey *ecdsa.PrivateKey, maxGas *big.Int, up bool) (*types.Transaction, *common.ErrorWithSeverity) {
   143  	noSendOpts, err := NewAuthorizedTransactor(c.ec, privateKey, 0, maxGas, big.NewInt(0))
   144  	if err != nil {
   145  		return nil, common.NewErrorWithSeverity(common.Critical, err.Error())
   146  	}
   147  	signedTx, err := c.Tomb.Flip(noSendOpts, up)
   148  	if err != nil {
   149  		return nil, common.NewErrorWithSeverity(common.Critical, err.Error())
   150  	}
   151  	return c.dryrunAndSend(noSendOpts.From, signedTx)
   152  }
   153  
   154  func (c *TombplusClient) Flipmultiple(privateKey *ecdsa.PrivateKey, maxGas *big.Int, epochs int64, up bool) (*types.Transaction, *common.ErrorWithSeverity) {
   155  	noSendOpts, err := NewAuthorizedTransactor(c.ec, privateKey, 0, maxGas, big.NewInt(0))
   156  	if err != nil {
   157  		return nil, common.NewErrorWithSeverity(common.Error, err.Error())
   158  	}
   159  
   160  	ups := make([]bool, epochs)
   161  	for i := int64(0); i < epochs; i++ {
   162  		ups[i] = up
   163  	}
   164  
   165  	signedTx, err := c.Tomb.FlipMultiple(noSendOpts, ups)
   166  	if err != nil {
   167  		return nil, common.NewErrorWithSeverity(common.Error, err.Error())
   168  	}
   169  	return c.dryrunAndSend(noSendOpts.From, signedTx)
   170  }
   171  
   172  func (c *TombplusClient) CheckResult(txHash string) *common.ErrorWithSeverity {
   173  	// Wait for the transaction to be mined
   174  	// receipt, err := bind.WaitMined(context.Background(), c.ec, tx)
   175  	receipt, err := c.ec.TransactionReceipt(context.Background(), ethCommon.HexToHash(txHash))
   176  	if err != nil {
   177  		// rpc error
   178  		return common.NewErrorWithSeverity(common.Error, err.Error())
   179  	}
   180  
   181  	if receipt.Status == 0 {
   182  		reasons := []string{}
   183  		for _, l := range receipt.Logs {
   184  			reasons = append(reasons, string(l.Data))
   185  		}
   186  
   187  		// onchain tx reverted, raise to Critical
   188  		return common.NewErrorWithSeverity(common.Critical, fmt.Sprintf("transaction %s was reverted with reason: %s", receipt.TxHash.Hex(), strings.Join(reasons, "\n")))
   189  	}
   190  
   191  	return nil
   192  }
   193  
   194  func (c *TombplusClient) dryrunAndSend(fromAddress ethCommon.Address, signedTx *types.Transaction) (*types.Transaction, *common.ErrorWithSeverity) {
   195  	// Dryrun first to save gas in case of revert
   196  	_, err := c.ec.CallContract(context.Background(), ethereum.CallMsg{
   197  		To:       signedTx.To(),
   198  		From:     fromAddress,
   199  		Gas:      signedTx.Gas(),
   200  		GasPrice: signedTx.GasPrice(),
   201  		Value:    big.NewInt(0),
   202  		Data:     signedTx.Data(),
   203  	}, nil)
   204  	if err != nil {
   205  		return nil, common.NewErrorWithSeverity(common.Critical, err.Error())
   206  	}
   207  
   208  	err = c.ec.SendTransaction(context.Background(), signedTx)
   209  	if err != nil {
   210  		// rpc error
   211  		return nil, common.NewErrorWithSeverity(common.Critical, err.Error())
   212  	}
   213  	return signedTx, nil
   214  }