diff --git a/common/model/secret_config.go b/common/model/secret_config.go index 6f6224f1..32ca88a9 100644 --- a/common/model/secret_config.go +++ b/common/model/secret_config.go @@ -23,10 +23,11 @@ type SecretConfig struct { SponsorConfig SponsorConfig `json:"sponsor_config"` } type SponsorConfig struct { - SponsorDepositAddress string `json:"sponsor_deposit_address"` - DashBoardSignerAddress string `json:"dashboard_signer_address"` - DepositTestNetUrl string `json:"deposit_test_net_url"` - DepositMainNetUrl string `json:"deposit_main_net_url"` + SponsorDepositAddress string `json:"sponsor_deposit_address"` + SponsorDepositPrivateKey string `json:"sponsor_deposit_private_key"` + DashBoardSignerAddress string `json:"dashboard_signer_address"` + DepositTestNetUrl string `json:"deposit_test_net_url"` + DepositMainNetUrl string `json:"deposit_main_net_url"` } type NetWorkSecretConfig struct { diff --git a/common/model/sponsor.go b/common/model/sponsor.go index e541b975..eacff4ab 100644 --- a/common/model/sponsor.go +++ b/common/model/sponsor.go @@ -9,12 +9,12 @@ type DepositSponsorRequest struct { DepositSource string `json:"deposit_source"` } type WithdrawSponsorRequest struct { - Amount float64 - - PayUserId string - IsTestNet bool - TxInfo map[string]string - TxHash string + Amount float64 + TimeStamp int64 `json:"time_stamp"` + PayUserId string + IsTestNet bool + RefundAddress string `json:"refund_address"` + DepositSource string `json:"deposit_source"` } type GetSponsorTransactionsRequest struct { } diff --git a/common/utils/util.go b/common/utils/util.go index a1b4253b..ca998bda 100644 --- a/common/utils/util.go +++ b/common/utils/util.go @@ -3,6 +3,7 @@ package utils import ( "AAStarCommunity/EthPaymaster_BackService/common/global_const" "bytes" + "context" "crypto/ecdsa" "encoding/hex" "fmt" @@ -10,11 +11,14 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/rpc" "github.com/sirupsen/logrus" "golang.org/x/xerrors" "gorm.io/gorm" + "log" "math/big" "regexp" "runtime" @@ -219,3 +223,46 @@ func DBTransactional(db *gorm.DB, handle func(tx *gorm.DB) error) (err error) { err = handle(tx) return err } + +func TransfertEth(from *ecdsa.PrivateKey, toAddress *common.Address, client *ethclient.Client) (*types.Transaction, error) { + fromPrivateKey := from.Public() + fromPublicKeyECDSA, ok := fromPrivateKey.(*ecdsa.PublicKey) + if !ok { + logrus.Error("error casting public key to ECDSA") + return nil, xerrors.Errorf("error casting public key to ECDSA") + } + fromAddress := crypto.PubkeyToAddress(*fromPublicKeyECDSA) + nonce, err := client.PendingNonceAt(context.Background(), fromAddress) + if err != nil { + return nil, err + + } + value := big.NewInt(100000000000000000) // in wei (0.1 eth) + gasLimit := uint64(21000) // in units + gasPrice, err := client.SuggestGasPrice(context.Background()) + if err != nil { + return nil, err + } + + var data []byte + tx := types.NewTx(&types.DynamicFeeTx{ + + Nonce: nonce, + Data: data, + Gas: gasLimit, + GasFeeCap: gasPrice, + GasTipCap: gasPrice, + Value: value, + To: toAddress, + }) + signTx, err := types.SignTx(tx, types.NewEIP155Signer(big.NewInt(1)), from) + + if err != nil { + log.Fatal(err) + } + err = client.SendTransaction(context.Background(), signTx) + if err != nil { + log.Fatal(err) + } + return signTx, nil +} diff --git a/config/secret_config.go b/config/secret_config.go index 7702943a..4d357ed2 100644 --- a/config/secret_config.go +++ b/config/secret_config.go @@ -12,9 +12,13 @@ var dsnTemplate = "host=%s port=%v user=%s password=%s dbname=%s TimeZone=%s ssl var secretConfig *model.SecretConfig var signerConfig = make(SignerConfigMap) +var depositer *global_const.EOA type SignerConfigMap map[global_const.Network]*global_const.EOA +func GetDepositer() *global_const.EOA { + return depositer +} func secretConfigInit(secretConfigPath string) { if secretConfigPath == "" { panic("secretConfigPath is empty") @@ -39,6 +43,10 @@ func secretConfigInit(secretConfigPath string) { signerConfig[global_const.Network(network)] = eoa } + depositer, err = global_const.NewEoa(secretConfig.SponsorConfig.SponsorDepositPrivateKey) + if err != nil { + panic(fmt.Sprintf("signer key error: %s", err)) + } } func IsSponsorWhitelist(address string) bool { diff --git a/rpc_server/api/v1/sponsor.go b/rpc_server/api/v1/sponsor.go index 42f731ac..668ced15 100644 --- a/rpc_server/api/v1/sponsor.go +++ b/rpc_server/api/v1/sponsor.go @@ -151,7 +151,7 @@ func validateDeposit(request *model.DepositSponsorRequest) (sender *common.Addre if tx.To() == nil { return nil, nil, xerrors.Errorf("Tx To Address is nil") } - if tx.To().Hex() != config.GetSponsorConfig().SponsorDepositAddress { + if tx.To().Hex() != config.GetDepositer().Address.String() { return nil, nil, xerrors.Errorf("Tx To Address is not Sponsor Address") } value := tx.Value() @@ -195,6 +195,10 @@ func GetInfoByHash(txHash string, client *ethclient.Client) (*types.Transaction, // @Router /api/v1/paymaster_sponsor/withdraw [post] // @Success 200 func WithdrawSponsor(ctx *gin.Context) { + client, err := ethclient.Dial("https://opt-sepolia.g.alchemy.com/v2/_z0GaU6Zk8RfIR1guuli8nqMdb8RPdp0") + if err != nil { + return + } request := model.WithdrawSponsorRequest{} response := model.GetResponse() if err := ctx.ShouldBindJSON(&request); err != nil { @@ -202,8 +206,47 @@ func WithdrawSponsor(ctx *gin.Context) { response.SetHttpCode(http.StatusBadRequest).FailCode(ctx, http.StatusBadRequest, errStr) return } - //TODO Add Signature Verification - result, err := sponsor_manager.WithDrawSponsor(&request) + //validate Sign + //validate Signature + inputJson, err := json.Marshal(request) + if err != nil { + response.SetHttpCode(http.StatusInternalServerError).FailCode(ctx, http.StatusInternalServerError, err.Error()) + return + } + var signerAddress string + if request.DepositSource == "dashboard" { + signerAddress = config.GetSponsorConfig().DashBoardSignerAddress + } else { + response.SetHttpCode(http.StatusBadRequest).FailCode(ctx, http.StatusBadRequest, "Deposit Source Error :Not Support Source") + return + } + err = ValidateSignature(ctx.GetHeader("relay_hash"), ctx.GetHeader("relay_signature"), inputJson, signerAddress) + if err != nil { + response.SetHttpCode(http.StatusBadRequest).FailCode(ctx, http.StatusBadRequest, err.Error()) + return + } + //checkBalance + balanceModel, err := sponsor_manager.FindUserSponsorBalance(request.PayUserId, request.IsTestNet) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + response.SetHttpCode(http.StatusBadRequest).FailCode(ctx, http.StatusBadRequest, "Balance Not Found") + return + } + response.SetHttpCode(http.StatusInternalServerError).FailCode(ctx, http.StatusInternalServerError, err.Error()) + return + } + amountFloat := big.NewFloat(request.Amount) + + if balanceModel.AvailableBalance.Cmp(amountFloat) < 0 { + response.SetHttpCode(http.StatusBadRequest).FailCode(ctx, http.StatusBadRequest, "Insufficient Balance") + return + } + toAddress := common.HexToAddress(request.RefundAddress) + // Execute transfer + tx, err := utils.TransfertEth(config.GetDepositer().PrivateKey, &toAddress, client) + logrus.Debugf("tx: %v", tx) + // WithDrawSponsor + result, err := sponsor_manager.WithDrawSponsor(&request, "") if err != nil { response.SetHttpCode(http.StatusInternalServerError).FailCode(ctx, http.StatusInternalServerError, err.Error()) return diff --git a/sponsor_manager/sponsor_service.go b/sponsor_manager/sponsor_service.go index fb5d612d..9aa38e46 100644 --- a/sponsor_manager/sponsor_service.go +++ b/sponsor_manager/sponsor_service.go @@ -256,7 +256,7 @@ func DepositSponsor(input *DepositSponsorInput) (*UserSponsorBalanceDBModel, err return balanceModel, txErr } -func WithDrawSponsor(input *model.WithdrawSponsorRequest) (*UserSponsorBalanceDBModel, error) { +func WithDrawSponsor(input *model.WithdrawSponsorRequest, txHash string) (*UserSponsorBalanceDBModel, error) { amount := big.NewFloat(input.Amount) balanceModel, err := FindUserSponsorBalance(input.PayUserId, input.IsTestNet) if err != nil { @@ -279,7 +279,7 @@ func WithDrawSponsor(input *model.WithdrawSponsorRequest) (*UserSponsorBalanceDB Source: "Withdraw", IsTestNet: input.IsTestNet, UpdateType: global_const.UpdateTypeWithdraw, - TxHash: input.TxHash, + TxHash: txHash, } if createErr := relayDB.Create(changeModel).Error; createErr != nil { return createErr