From 8babd56ca7a4ce1cd3a2e5f872121a62aba1d5cb Mon Sep 17 00:00:00 2001 From: perror <23651751+perrornet@users.noreply.github.com> Date: Tue, 20 Aug 2024 11:34:48 +0800 Subject: [PATCH] Add mantle multisig support --- utils/provider/bridge/routernitro/utils.go | 3 +- utils/wallets/dsafe/utils.go | 4 +- utils/wallets/mantle_safe/safe.go | 212 +++++++++ utils/wallets/mantle_safe/utils.go | 491 +++++++++++++++++++++ utils/wallets/multisig/multisig.go | 3 + 5 files changed, 710 insertions(+), 3 deletions(-) create mode 100644 utils/wallets/mantle_safe/safe.go create mode 100644 utils/wallets/mantle_safe/utils.go diff --git a/utils/provider/bridge/routernitro/utils.go b/utils/provider/bridge/routernitro/utils.go index 94477c4..11ee306 100644 --- a/utils/provider/bridge/routernitro/utils.go +++ b/utils/provider/bridge/routernitro/utils.go @@ -127,9 +127,10 @@ func (r Routernitro) GetBestQuote(ctx context.Context, args provider.SwapParams) log.Debugf("#%d %s %s get quote error: %s", args.OrderId, msg, tokenIn.Name, err) return } + tokenAmount := quoteData.Get("destination").Get("tokenAmount").String() if tokenAmount == "" { - log.Warnf("#%d %s %s get quote error: destination token amount is empty", args.OrderId, msg, tokenIn.Name) + log.Warnf("#%d %s %s get quote error: destination token amount is empty: %s", args.OrderId, msg, tokenIn.Name, quoteData.Raw) return } diff --git a/utils/wallets/dsafe/utils.go b/utils/wallets/dsafe/utils.go index fb03cae..796d13f 100644 --- a/utils/wallets/dsafe/utils.go +++ b/utils/wallets/dsafe/utils.go @@ -272,9 +272,9 @@ func (s *Dsafe) getOperatorAddress() common.Address { } func (s *Dsafe) nonce(ctx context.Context) (int64, error) { - u := fmt.Sprintf("https://dsafe.dcdao.box/cgw/v1/chains/46/safes/%s", s.getOperatorSafeAddress().Hex()) + u := fmt.Sprintf("https://dsafe.dcdao.box/cgw/v1/chains/46/safes/%s/nonces", s.getOperatorSafeAddress().Hex()) var dest = struct { - RecommendedNonce int64 `json:"nonce"` + RecommendedNonce int64 `json:"recommendedNonce"` }{} return dest.RecommendedNonce, utils.Request(ctx, "GET", u, nil, &dest) } diff --git a/utils/wallets/mantle_safe/safe.go b/utils/wallets/mantle_safe/safe.go new file mode 100644 index 0000000..d025c45 --- /dev/null +++ b/utils/wallets/mantle_safe/safe.go @@ -0,0 +1,212 @@ +package mantle_safe + +import ( + "context" + "encoding/json" + "fmt" + "omni-balance/utils" + "omni-balance/utils/chains" + "omni-balance/utils/constant" + "omni-balance/utils/error_types" + "omni-balance/utils/notice" + "omni-balance/utils/wallets" + "strings" + "time" + + log "omni-balance/utils/logging" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient/simulated" + "github.com/pkg/errors" + "github.com/shopspring/decimal" + "github.com/spf13/cast" + "go.uber.org/zap/zapcore" +) + +type MantleSafe struct { + conf wallets.WalletConfig + operatorSafe *MantleSafe +} + +func NewMantleSafe(conf wallets.WalletConfig) *MantleSafe { + if conf.Operator.Address.Cmp(constant.ZeroAddress) == 0 { + panic("operator address is empty") + } + if conf.Operator.Address.Cmp(conf.Address) == 0 { + panic("operator address is same as safe address") + } + s := &MantleSafe{ + conf: conf, + } + if conf.Operator.MultiSignType != "" { + operator := conf.Operator + s.operatorSafe = &MantleSafe{ + conf: wallets.WalletConfig{ + PrivateKey: operator.PrivateKey, + Address: operator.Address, + Operator: wallets.Operator{ + Address: operator.Operator, + PrivateKey: operator.PrivateKey, + }, + MultiSignType: operator.MultiSignType, + }, + } + } + return s +} + +func (*MantleSafe) IsSupportEip712() bool { + return false +} + +func (s *MantleSafe) CheckFullAccess(ctx context.Context) error { + info, err := s.safeWalletInfo(ctx) + if err != nil { + return err + } + var owners []string + for _, v := range info.Owners { + owners = append(owners, v.Value) + } + if !utils.InArrayFold(s.conf.Operator.Address.Hex(), owners) { + return errors.New("operator is not in owners") + } + return nil +} + +// GetAddress retrieves the address from the security configuration. +// If isReal is provided as true and the multisig type is set, +// it returns the operator's address; otherwise, it returns the default address. +// Parameters: +// +// isReal - An optional boolean indicating whether to return the real address. +// +// Returns: +// +// common.Address - The address from the configuration. +func (s *MantleSafe) GetAddress(isReal ...bool) common.Address { + if len(isReal) > 0 && isReal[0] { + if s.conf.Operator.MultiSignType == "" { + return s.conf.Address + } + return s.conf.Operator.Address + } + return s.conf.Address +} + +func (s *MantleSafe) IsDifferentAddress() bool { + return true +} + +func (s *MantleSafe) SignRawMessage(_ []byte) (sig []byte, err error) { + return nil, error_types.ErrUnsupportedWalletType +} + +func (s *MantleSafe) GetExternalBalance(ctx context.Context, tokenAddress common.Address, decimals int32, + client simulated.Client) (decimal.Decimal, error) { + return s.GetBalance(ctx, tokenAddress, decimals, client) +} + +func (s *MantleSafe) GetBalance(ctx context.Context, tokenAddress common.Address, decimals int32, + client simulated.Client) (decimal.Decimal, error) { + return chains.GetTokenBalance(ctx, client, tokenAddress.Hex(), s.GetAddress().Hex(), decimals) +} + +func (s *MantleSafe) GetNonce(ctx context.Context, client simulated.Client) (uint64, error) { + if s.operatorSafe != nil { + nonce, err := s.operatorSafe.nonce(ctx) + log.Debugf("operator %s safe nonce: %d", s.conf.Operator.Address.Hex(), nonce) + return uint64(nonce), err + } + return client.NonceAt(ctx, s.GetAddress(), nil) +} + +func (s *MantleSafe) SendTransaction(ctx context.Context, tx *types.LegacyTx, client simulated.Client) (common.Hash, error) { + return s.MultisigTransaction(ctx, tx, client) +} + +func (s *MantleSafe) WaitTransaction(ctx context.Context, txHash common.Hash, _ simulated.Client) error { + var ( + t = time.NewTicker(time.Second * 10) + count = 0 + ) + defer t.Stop() + for count < 60 { + select { + case <-ctx.Done(): + return context.Canceled + case <-t.C: + tx, err := s.GetMultiSigTransaction(ctx, txHash) + if err != nil && strings.Contains(err.Error(), "No MultisigTransaction matches the given query") { + return errors.Wrap(err, "transaction not found") + } + if err != nil { + log.Debugf("wait transaction %s error: %s", txHash, err) + continue + } + if len(tx.DetailedExecutionInfo.Confirmations) < tx.DetailedExecutionInfo.ConfirmationsRequired { + count = 0 + log.Infof("%s transaction %s confirmations: %d, required: %d,", + tx.SafeAddress, txHash, len(tx.DetailedExecutionInfo.Confirmations), tx.DetailedExecutionInfo.ConfirmationsRequired) + if err := notice.Send(ctx, + fmt.Sprintf("wait %s safeHash %s confirmations and execute.", + constant.GetChainName(s.GetChainIdByCtx(ctx)), txHash), + fmt.Sprintf("Please go to https://multisig.mantle.xyz/transactions/queue?safe=mantle:%s to confirm and execute #%d transaction.", + tx.SafeAddress, tx.DetailedExecutionInfo.Nonce), + zapcore.WarnLevel, + ); err != nil { + log.Debugf("send notice error: %s", err) + } + continue + } + if tx.ExecutedAt == nil || *tx.ExecutedAt <= 0 { + count = 0 + log.Debugf("transaction %s is not executed", txHash) + continue + } + if !strings.EqualFold(tx.TxStatus, "SUCCESS") { + log.Debugf("transaction %s status is %s", txHash, tx.TxStatus) + count++ + continue + } + log.Debugf("transaction %s is successful", txHash) + return nil + } + } + return errors.Errorf("wait transaction %s timeout", txHash) +} + +func (s *MantleSafe) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]interface{}{ + "address": s.conf.Address.Hex(), + "operator": map[string]interface{}{ + "address": s.conf.Operator.Address.Hex(), + "operator": s.conf.Operator.Operator.Hex(), + "multi_sign_type": s.conf.MultiSignType, + }, + "multi_sign_type": s.conf.MultiSignType, + }) +} + +func (s *MantleSafe) GetRealHash(ctx context.Context, txHash common.Hash, client simulated.Client) (common.Hash, error) { + tx, err := s.GetMultiSigTransaction(ctx, txHash) + if err != nil { + return [32]byte{}, err + } + if len(tx.DetailedExecutionInfo.Confirmations) != tx.DetailedExecutionInfo.ConfirmationsRequired { + return [32]byte{}, errors.New("transaction not confirmed") + } + if tx.TxHash == "" { + return [32]byte{}, errors.New("transaction hash is empty") + } + txHashStr := cast.ToString(tx.TxHash) + if txHashStr == "" { + return [32]byte{}, errors.Errorf("transaction hash convert to hash error, the result is %+v", tx.TxHash) + } + return common.HexToHash(txHashStr), nil +} + +func (s *MantleSafe) Name(_ context.Context) string { + return "dsafe" +} diff --git a/utils/wallets/mantle_safe/utils.go b/utils/wallets/mantle_safe/utils.go new file mode 100644 index 0000000..818f56c --- /dev/null +++ b/utils/wallets/mantle_safe/utils.go @@ -0,0 +1,491 @@ +package mantle_safe + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "math/big" + "omni-balance/utils" + "omni-balance/utils/chains" + "omni-balance/utils/constant" + "omni-balance/utils/wallets/safe" + "omni-balance/utils/wallets/safe/safe_abi" + "strings" + "sync" + + log "omni-balance/utils/logging" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient/simulated" + "github.com/ethereum/go-ethereum/signer/core/apitypes" + "github.com/pkg/errors" + "github.com/shopspring/decimal" + "github.com/spf13/cast" + "github.com/tidwall/gjson" +) + +var ( + safeGlobalLocker sync.Mutex +) + +type SafeResp struct { + Code int `json:"code"` + Message string `json:"message"` +} + +type ProposeTransaction struct { + To string `json:"to"` + Value string `json:"value"` + Data string `json:"data"` + Operation int `json:"operation"` + BaseGas string `json:"baseGas"` + GasPrice string `json:"gasPrice"` + GasToken string `json:"gasToken"` + RefundReceiver string `json:"refundReceiver"` + Nonce string `json:"nonce"` + SafeTxGas string `json:"safeTxGas"` + SafeTxHash string `json:"safeTxHash"` + Sender string `json:"sender"` + Signature string `json:"signature"` +} + +type Transaction struct { + SafeResp + SafeAddress string `json:"safeAddress"` + TxID string `json:"txId"` + ExecutedAt *int64 `json:"executedAt"` + TxStatus string `json:"txStatus"` + TxInfo struct { + Type string `json:"type"` + HumanDescription any `json:"humanDescription"` + Sender struct { + Value string `json:"value"` + Name any `json:"name"` + LogoURI any `json:"logoUri"` + } `json:"sender"` + Recipient struct { + Value string `json:"value"` + Name any `json:"name"` + LogoURI any `json:"logoUri"` + } `json:"recipient"` + Direction string `json:"direction"` + TransferInfo struct { + Type string `json:"type"` + Value string `json:"value"` + } `json:"transferInfo"` + } `json:"txInfo"` + TxData struct { + HexData any `json:"hexData"` + DataDecoded any `json:"dataDecoded"` + To struct { + Value string `json:"value"` + Name any `json:"name"` + LogoURI any `json:"logoUri"` + } `json:"to"` + Value string `json:"value"` + Operation int `json:"operation"` + TrustedDelegateCallTarget any `json:"trustedDelegateCallTarget"` + AddressInfoIndex any `json:"addressInfoIndex"` + } `json:"txData"` + TxHash string `json:"txHash"` + DetailedExecutionInfo struct { + Type string `json:"type"` + SubmittedAt int64 `json:"submittedAt"` + Nonce int `json:"nonce"` + SafeTxGas string `json:"safeTxGas"` + BaseGas string `json:"baseGas"` + GasPrice string `json:"gasPrice"` + GasToken string `json:"gasToken"` + RefundReceiver struct { + Value string `json:"value"` + Name any `json:"name"` + LogoURI any `json:"logoUri"` + } `json:"refundReceiver"` + SafeTxHash string `json:"safeTxHash"` + Executor struct { + Value string `json:"value"` + Name any `json:"name"` + LogoURI any `json:"logoUri"` + } `json:"executor"` + Signers []struct { + Value string `json:"value"` + Name any `json:"name"` + LogoURI any `json:"logoUri"` + } `json:"signers"` + ConfirmationsRequired int `json:"confirmationsRequired"` + Confirmations []struct { + Signer struct { + Value string `json:"value"` + Name any `json:"name"` + LogoURI any `json:"logoUri"` + } `json:"signer"` + Signature string `json:"signature"` + SubmittedAt int64 `json:"submittedAt"` + } `json:"confirmations"` + Rejectors []any `json:"rejectors"` + GasTokenInfo any `json:"gasTokenInfo"` + Trusted bool `json:"trusted"` + Proposer struct { + Value string `json:"value"` + Name any `json:"name"` + LogoURI any `json:"logoUri"` + } `json:"proposer"` + } `json:"detailedExecutionInfo"` + SafeAppInfo any `json:"safeAppInfo"` +} + +type Info struct { + SafeResp + Address struct { + Value string `json:"value"` + Name any `json:"name"` + LogoURI any `json:"logoUri"` + } `json:"address"` + ChainID string `json:"chainId"` + Nonce int `json:"nonce"` + Threshold int `json:"threshold"` + Owners []struct { + Value string `json:"value"` + Name any `json:"name"` + LogoURI any `json:"logoUri"` + } `json:"owners"` + Implementation struct { + Value string `json:"value"` + Name string `json:"name"` + LogoURI string `json:"logoUri"` + } `json:"implementation"` + ImplementationVersionState string `json:"implementationVersionState"` + CollectiblesTag any `json:"collectiblesTag"` + TxQueuedTag string `json:"txQueuedTag"` + TxHistoryTag string `json:"txHistoryTag"` + MessagesTag any `json:"messagesTag"` + Modules any `json:"modules"` + FallbackHandler struct { + Value string `json:"value"` + Name string `json:"name"` + LogoURI string `json:"logoUri"` + } `json:"fallbackHandler"` + Guard any `json:"guard"` + Version string `json:"version"` +} + +func (s *MantleSafe) GetDomainByCtx(ctx context.Context) string { + return constant.Mantle +} + +func (s *MantleSafe) GetChainIdByCtx(ctx context.Context) int { + return constant.GetChainId(s.GetDomainByCtx(ctx)) +} + +func (s *MantleSafe) safeWalletInfo(ctx context.Context) (*Info, error) { + var address = s.getOperatorSafeAddress().Hex() + var result = new(Info) + u := fmt.Sprintf("https://gateway.multisig.mantle.xyz/v1/chains/5000/safes/%s", address) + if err := utils.Request(ctx, "GET", u, nil, &result); err != nil { + return nil, err + } + return result, nil +} + +func (s *MantleSafe) Transfer(ctx context.Context, tx *types.LegacyTx, client simulated.Client) (common.Hash, error) { + if s.operatorSafe != nil { + return s.operatorSafe.SendTransaction(ctx, tx, client) + } + return chains.SendTransaction(ctx, client, tx, s.GetAddress(true), s.conf.Operator.PrivateKey) +} + +func (s *MantleSafe) MultisigTransaction(ctx context.Context, tx *types.LegacyTx, client simulated.Client) (common.Hash, error) { + if tx.Nonce == 0 { + nonce, err := s.nonce(ctx) + if err != nil { + return common.Hash{}, errors.Wrap(err, "get nonce error") + } + tx.Nonce = uint64(nonce) + log.Debugf("%s nonce: %d", s.getOperatorSafeAddress(), tx.Nonce) + } + + to, _, err := chains.GetTransferInfo(tx.Data) + if err == nil && s.GetAddress().Cmp(to) == 0 { + log.Debugf("transfer to self") + return s.Transfer(ctx, tx, client) + } + + // _, err = client.EstimateGas(ctx, ethereum.CallMsg{ + // From: s.GetAddress(), + // To: tx.To, + // Value: tx.Value, + // Data: tx.Data, + // }) + // if err != nil { + // log.Debugf("estimate gas error: %s", err.Error()) + // return common.Hash{}, errors.Wrap(err, "estimate gas error") + // } + + info, err := s.safeWalletInfo(ctx) + if err != nil { + return common.Hash{}, err + } + safeTxHash, err := s.proposeTransaction(ctx, tx) + if err != nil { + return common.Hash{}, errors.Wrap(err, "propose transaction error") + } + log.Debugf("safe tx hash: %s", safeTxHash) + if info.Threshold > 1 { + return safeTxHash, nil + } + + txInfo, err := s.GetMultiSigTransaction(ctx, safeTxHash) + if err != nil { + return safeTxHash, errors.Wrap(err, "get multisig transaction error") + } + if txInfo.ExecutedAt != nil && *txInfo.ExecutedAt > 0 { + return safeTxHash, errors.New("transaction already executed") + } + return safeTxHash, s.ExecTransaction(ctx, txInfo, client) +} + +func (s *MantleSafe) getOperatorSafeAddress() common.Address { + if s.conf.Operator.MultiSignType != "" { + return s.conf.Operator.Address + } + return s.conf.Address +} + +func (s *MantleSafe) getOperatorAddress() common.Address { + if s.conf.Operator.MultiSignType != "" { + return s.conf.Operator.Operator + } + return s.conf.Operator.Address +} + +func (s *MantleSafe) nonce(ctx context.Context) (int64, error) { + u := fmt.Sprintf("https://gateway.multisig.mantle.xyz/v1/chains/5000/safes/%s/nonces", s.getOperatorSafeAddress().Hex()) + var dest = struct { + RecommendedNonce int64 `json:"recommendedNonce"` + }{} + return dest.RecommendedNonce, utils.Request(ctx, "GET", u, nil, &dest) +} + +func (s *MantleSafe) proposeTransaction(ctx context.Context, tx *types.LegacyTx) (common.Hash, error) { + safeGlobalLocker.Lock() + defer safeGlobalLocker.Unlock() + nonce, err := s.nonce(ctx) + if err != nil { + return common.Hash{}, errors.Wrap(err, "get nonce error") + } + + t := &safe.Transaction{ + Safe: s.GetAddress(true), + To: *tx.To, + Data: common.Bytes2Hex(tx.Data), + Nonce: int(nonce), + } + if tx.Value != nil { + t.Value = decimal.NewFromBigInt(tx.Value, 0) + } + + typedData := s.eip712(ctx, *t) + + var safeTxHash string + sigData, err := chains.SignTypedData(typedData, func(msg []byte) (sig []byte, err error) { + safeTxHash = common.Bytes2Hex(msg) + privateKey := s.conf.PrivateKey + if s.conf.Operator.PrivateKey != "" { + privateKey = s.conf.Operator.PrivateKey + } + return chains.SignMsg(msg, privateKey) + }) + if err != nil { + return common.Hash{}, errors.Wrap(err, "sign typed data error") + } + txData, err := json.Marshal(ProposeTransaction{ + To: t.To.Hex(), + Value: t.Value.String(), + Data: "0x" + t.Data, + BaseGas: "0", + GasPrice: "0", + GasToken: constant.ZeroAddress.Hex(), + RefundReceiver: constant.ZeroAddress.Hex(), + Nonce: cast.ToString(t.Nonce), + SafeTxGas: "0", + SafeTxHash: fmt.Sprintf("0x%s", safeTxHash), + Sender: s.getOperatorAddress().Hex(), + Signature: "0x" + common.Bytes2Hex(sigData), + }) + if err != nil { + return common.Hash{}, errors.Wrap(err, "marshal transaction error") + } + + data, err := utils.RequestBinary( + ctx, "POST", + fmt.Sprintf("https://gateway.multisig.mantle.xyz/v1/chains/5000/transactions/%s/propose", s.GetAddress(true).Hex()), + bytes.NewReader(txData)) + if err != nil { + return common.Hash{}, nil + } + result := gjson.ParseBytes(data) + if result.Get("statusCode").Int() != 0 || result.Get("code").Int() != 0 { + return common.Hash{}, errors.New(result.Get("message").String()) + } + + return common.HexToHash(fmt.Sprintf("0x%s", safeTxHash)), nil +} + +func (s *MantleSafe) GetMultiSigTransaction(ctx context.Context, safeTxHash common.Hash) (Transaction, error) { + u := fmt.Sprintf("https://gateway.multisig.mantle.xyz/v1/chains/5000/transactions/multisig_%s_%s", s.getOperatorSafeAddress().Hex(), safeTxHash.Hex()) + var result Transaction + if err := utils.Request(ctx, "GET", u, nil, &result); err != nil { + return result, errors.Wrap(err, "get multisig transaction error") + } + if result.Code != 0 { + return result, errors.New(result.Message) + } + return result, nil +} + +func (s *MantleSafe) ExecTransaction(ctx context.Context, tx Transaction, client simulated.Client) error { + abi, err := safe_abi.SafeAbiMetaData.GetAbi() + if err != nil { + return errors.Wrap(err, "get abi error") + } + nonce, err := client.NonceAt(context.TODO(), s.GetAddress(true), nil) + if err != nil { + return errors.Wrap(err, "get nonce error") + } + var ( + signatures []string + hasSelf = false + ) + + for _, v := range tx.DetailedExecutionInfo.Confirmations { + if strings.EqualFold(v.Signer.Value, s.GetAddress(true).Hex()) { + hasSelf = true + } + signatures = append(signatures, strings.TrimPrefix(v.Signature, "0x")) + } + + var signatureData []byte + if !hasSelf && len(signatures) != 0 { + address := common.Bytes2Hex(common.LeftPadBytes(s.GetAddress(true).Bytes(), 32)) + confriom := common.Bytes2Hex(common.LeftPadBytes(big.NewInt(1).Bytes(), 33)) + signatureData = common.Hex2Bytes(fmt.Sprintf("%s%s", address, confriom)) + } else { + proposeTx := &safe.Transaction{ + Safe: s.GetAddress(true), + To: common.HexToAddress(tx.TxData.To.Value), + Data: "0x", + Nonce: int(tx.DetailedExecutionInfo.Nonce), + } + if tx.TxData.HexData != nil { + proposeTx.Data = cast.ToString(tx.TxData.HexData) + } + signatureData, err = chains.SignTypedData(s.eip712(ctx, *proposeTx), func(msg []byte) (sig []byte, err error) { + return chains.SignMsg(msg, s.conf.PrivateKey) + }) + if err != nil { + return errors.Wrap(err, "sign error") + } + } + + if len(signatures) < tx.DetailedExecutionInfo.ConfirmationsRequired && hasSelf { + return errors.New("not enough signatures") + } + + if len(signatures) < tx.DetailedExecutionInfo.ConfirmationsRequired { + signatures = append(signatures, common.Bytes2Hex(signatureData)) + } + + if len(signatures) < tx.DetailedExecutionInfo.ConfirmationsRequired { + return errors.New("not enough signatures") + } + data := "0x" + if tx.TxData.HexData != nil { + data = cast.ToString(tx.TxData.HexData) + } + input, err := abi.Pack("execTransaction", + tx.TxData.To.Value, + decimal.RequireFromString(tx.TxData.Value).IntPart(), + common.Hex2Bytes(strings.TrimPrefix(data, "0x")), + uint8(tx.TxData.Operation), + cast.ToInt64(tx.DetailedExecutionInfo.SafeTxGas), + cast.ToInt64(tx.DetailedExecutionInfo.BaseGas), + cast.ToInt64(tx.DetailedExecutionInfo.GasPrice), + tx.DetailedExecutionInfo.GasToken, + tx.DetailedExecutionInfo.RefundReceiver.Value, + common.Hex2Bytes(strings.Join(signatures, "")), + ) + if err != nil { + return errors.Wrap(err, "pack error") + } + gasPrice, err := client.SuggestGasPrice(ctx) + if err != nil { + return errors.Wrap(err, "suggest gas price error") + } + to := common.HexToAddress(tx.TxData.To.Value) + gas, err := client.EstimateGas(context.TODO(), ethereum.CallMsg{ + From: s.GetAddress(true), + To: &to, + GasPrice: gasPrice, + Data: input, + }) + if err != nil { + return errors.Wrap(err, "estimate gas error") + } + chainTransaction := types.NewTx(&types.LegacyTx{ + Nonce: nonce, + GasPrice: gasPrice, + Gas: gas, + To: &to, + Data: input, + }) + + signTx, err := chains.SignTx(chainTransaction, s.conf.PrivateKey, int64(s.GetChainIdByCtx(ctx))) + if err != nil { + return errors.Wrap(err, "sign tx error") + } + return client.SendTransaction(ctx, signTx) +} + +func (s *MantleSafe) eip712(ctx context.Context, t safe.Transaction) apitypes.TypedData { + return apitypes.TypedData{ + Types: apitypes.Types{ + "EIP712Domain": []apitypes.Type{ + {Name: "chainId", Type: "uint256"}, + {Name: "verifyingContract", Type: "address"}, + }, + "SafeTx": []apitypes.Type{ + {Type: "address", Name: "to"}, + {Type: "uint256", Name: "value"}, + {Type: "bytes", Name: "data"}, + {Type: "uint8", Name: "operation"}, + {Type: "uint256", Name: "safeTxGas"}, + {Type: "uint256", Name: "baseGas"}, + {Type: "uint256", Name: "gasPrice"}, + {Type: "address", Name: "gasToken"}, + {Type: "address", Name: "refundReceiver"}, + {Type: "uint256", Name: "nonce"}, + }, + }, + PrimaryType: "SafeTx", + Domain: apitypes.TypedDataDomain{ + ChainId: math.NewHexOrDecimal256(int64(s.GetChainIdByCtx(ctx))), + VerifyingContract: t.Safe.Hex(), + }, + Message: apitypes.TypedDataMessage{ + "to": t.To.Hex(), + "value": t.Value.BigInt(), + "data": common.Hex2Bytes(t.Data), + "operation": big.NewInt(int64(t.Operation)), + "baseGas": t.BaseGas.BigInt(), + "gasPrice": t.GasPrice.BigInt(), + "gasToken": t.GasToken.Hex(), + "refundReceiver": t.RefundReceiver.Hex(), + "nonce": big.NewInt(int64(t.Nonce)), + "safeTxGas": t.SafeTxGas.BigInt(), + }, + } +} diff --git a/utils/wallets/multisig/multisig.go b/utils/wallets/multisig/multisig.go index dfbf38a..3d14c2c 100644 --- a/utils/wallets/multisig/multisig.go +++ b/utils/wallets/multisig/multisig.go @@ -8,6 +8,7 @@ import ( "omni-balance/utils/error_types" "omni-balance/utils/wallets" "omni-balance/utils/wallets/dsafe" + "omni-balance/utils/wallets/mantle_safe" "omni-balance/utils/wallets/safe" "github.com/ethereum/go-ethereum/common" @@ -53,6 +54,8 @@ func (m Multisig) getRealInstance(ctx context.Context) wallets.Wallets { switch m.GetChainNameByCtx(ctx) { case constant.DarwiniaDvm: return dsafe.NewDsafe(m.conf) + case constant.Mantle: + return mantle_safe.NewMantleSafe(m.conf) default: return safe.NewSafe(m.conf) }