package service import ( "context" "encoding/hex" "encoding/json" "errors" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" tronaddr "github.com/fbsobreira/gotron-sdk/pkg/address" troncommon "github.com/fbsobreira/gotron-sdk/pkg/common" "github.com/fbsobreira/gotron-sdk/pkg/proto/api" troncore "github.com/fbsobreira/gotron-sdk/pkg/proto/core" "google.golang.org/grpc" "google.golang.org/protobuf/proto" "key-manager/service" "math/big" "sync" "wallet-server/conf" "wallet-server/dao" ) const ( ethNetwork = "eth" tronNetwork = "tron" ethCoin = "eth" trxCoin = "trx" usdtCoin = "usdt" usdcCoin = "usdc" ) type WalletServer struct { UnimplementedWalletServerServer lk sync.Mutex node *Node dao *dao.Dao keyManager service.KeyManagerClient } func NewWalletServer(dao *dao.Dao) (*WalletServer, error) { config := conf.GetConfig() node, err := NewNode(config.EthRpcAddrs, config.TronRpcAddrs) if err != nil { return nil, err } keyManagerClient, _, err := newKeyManagerClient(config.KeyManagerAddr) if err != nil { return nil, err } return &WalletServer{ node: node, dao: dao, keyManager: keyManagerClient, }, nil } func newKeyManagerClient(keyManagerAddr string) (service.KeyManagerClient, *grpc.ClientConn, error) { conn, err := grpc.Dial(keyManagerAddr, grpc.WithInsecure()) if err != nil { return nil, nil, err } client := service.NewKeyManagerClient(conn) return client, conn, nil } func (w *WalletServer) Transfer(ctx context.Context, req *TransferRequest) (*TransferResponse, error) { network := req.Network if network != ethNetwork && network != tronNetwork { return &TransferResponse{ Req: req, Code: networkErrCode, Msg: networkErrMsg, TxHash: "", }, nil } coin := req.Coin if coin != ethCoin && coin != trxCoin && coin != usdcCoin && coin != usdtCoin { return &TransferResponse{ Req: req, Code: coinErrCode, Msg: coinErrMsg, TxHash: "", }, nil } from, err1 := format(network, req.From) to, err2 := format(network, req.To) if err1 != nil || err2 != nil { return &TransferResponse{ Req: req, Code: addressErrCode, Msg: addressErrMsg, TxHash: "", }, nil } req.From = from req.To = to amount, ok := big.NewInt(0).SetString(req.Amount, 10) if !ok || amount.Cmp(big.NewInt(0)) <= 0 { return &TransferResponse{ Req: req, Code: amountErrCode, Msg: amountErrMsg, TxHash: "", }, nil } res, err := w.keyManager.GetIndex(ctx, &service.GetIndexRequest{ Network: network, Address: from, }) if err != nil { return &TransferResponse{ Req: req, Code: keyManagerErrCode, Msg: err.Error(), TxHash: "", }, nil } if res.Code != okCode { return &TransferResponse{ Req: req, Code: res.Code, Msg: res.Msg, TxHash: "", }, nil } var txStr string var txHash string if network == ethNetwork { var ethTx *types.Transaction code := serverErrCode if coin == ethCoin { ethTx, err = buildTransferEth(w.node.ethClient, req.From, req.To, amount) } else if coin == usdcCoin { ethTx, err = buildTransferUsdcOfEth(w.node.ethClient, req.From, req.To, amount) } else if coin == usdtCoin { ethTx, err = buildTransferUsdtOfEth(w.node.ethClient, req.From, req.To, amount) } else { code = coinErrCode err = errors.New("coin and network do not match") } if err != nil { return &TransferResponse{ Req: req, Code: code, Msg: err.Error(), TxHash: "", }, nil } txStr, err = marshalJEthTx(ethTx) if err != nil { return &TransferResponse{ Req: req, Code: serverErrCode, Msg: err.Error(), TxHash: "", }, nil } } else { var tronTx *api.TransactionExtention code := serverErrCode if coin == trxCoin { tronTx, err = buildTransferTrx(w.node.tronClient, req.From, req.To, amount.Int64()) } else if coin == usdcCoin { tronTx, err = buildTransferUsdcOfTron(w.node.tronClient, req.From, req.To, amount) } else if coin == usdtCoin { tronTx, err = buildTransferUsdtOfTron(w.node.tronClient, req.From, req.To, amount) } else { code = coinErrCode err = errors.New("coin and network do not match") } if err != nil { return &TransferResponse{ Req: req, Code: code, Msg: err.Error(), TxHash: "", }, nil } txStr, err = marshalJTronTx(tronTx.Transaction) if err != nil { return &TransferResponse{ Req: req, Code: serverErrCode, Msg: err.Error(), TxHash: "", }, nil } txHash = troncommon.BytesToHexString(tronTx.GetTxid()) } signRes, err := w.keyManager.Sign(ctx, &service.SignRequest{ Network: network, Sender: from, Tx: txStr, }) if err != nil { return &TransferResponse{ Req: req, Code: keyManagerErrCode, Msg: err.Error(), TxHash: "", }, nil } if signRes.Code != okCode { return &TransferResponse{ Req: req, Code: signRes.Code, Msg: signRes.Msg, TxHash: "", }, nil } if network == ethNetwork { tx, err := unmarshalJEthTx(signRes.SignedTx) if err != nil { return &TransferResponse{ Req: req, Code: serverErrCode, Msg: err.Error(), TxHash: "", }, nil } err = w.node.ethClient.SendTransaction(ctx, tx) if err != nil { return &TransferResponse{ Req: req, Code: serverErrCode, Msg: err.Error(), TxHash: "", }, nil } txHash = tx.Hash().String() } else { tx, err := unmarshalJTronTx(signRes.SignedTx) if err != nil { return &TransferResponse{ Req: req, Code: serverErrCode, Msg: err.Error(), TxHash: "", }, nil } result, err := w.node.tronClient.Broadcast(tx) if result.Code != 0 || !result.Result { d, _ := json.Marshal(result) return &TransferResponse{ Req: req, Code: serverErrCode, Msg: string(d), TxHash: "", }, nil } } err = w.dao.CreateTransaction(&dao.Transaction{ TxId: req.Id, Network: req.Network, Coin: req.Coin, From: req.From, To: req.To, Amount: req.Amount, TxHash: txHash, }) if err != nil { log.Warnw("save transaction to mysql failed", "id", req.Id, "network", req.Network, "coin", req.Coin, "from", req.From, "to", req.To, "amount", req.Amount, "txhash", txHash, "err", err) } log.Infow("send transaction to chain", "id", req.Id, "network", req.Network, "coin", req.Coin, "from", req.From, "to", req.To, "amount", req.Amount, "txhash", txHash) return &TransferResponse{ Req: req, Code: okCode, Msg: okMsg, TxHash: txHash, }, nil } func format(network, address string) (string, error) { if network == ethNetwork { if address[:2] != "0x" { return "", errors.New("invalid address") } address = common.HexToAddress(address).String() } else { if address[:1] != "T" { return "", errors.New("invalid address") } addr, err := tronaddr.Base58ToAddress(address) if err != nil { return "", err } address = addr.String() } return address, nil } func marshalJEthTx(transaction *types.Transaction) (string, error) { b, err := transaction.MarshalJSON() if err != nil { return "", err } return hex.EncodeToString(b), nil } func unmarshalJEthTx(tx string) (*types.Transaction, error) { b, err := hex.DecodeString(tx) if err != nil { return nil, err } var transaction = &types.Transaction{} err = transaction.UnmarshalJSON(b) if err != nil { return nil, err } return transaction, err } func marshalJTronTx(transaction *troncore.Transaction) (string, error) { b, err := proto.Marshal(transaction) if err != nil { return "", err } return hex.EncodeToString(b), nil } func unmarshalJTronTx(tx string) (*troncore.Transaction, error) { b, err := hex.DecodeString(tx) if err != nil { return nil, err } var transaction troncore.Transaction err = proto.Unmarshal(b, &transaction) if err != nil { return nil, err } return &transaction, nil }