github.com/status-im/status-go@v1.1.0/services/wallet/transfer/helpers.go (about)

     1  package transfer
     2  
     3  import (
     4  	"database/sql"
     5  	"encoding/hex"
     6  	"errors"
     7  	"fmt"
     8  	"math/big"
     9  	"time"
    10  
    11  	"github.com/ethereum/go-ethereum/common"
    12  	"github.com/ethereum/go-ethereum/common/hexutil"
    13  	"github.com/ethereum/go-ethereum/log"
    14  	"github.com/status-im/status-go/account"
    15  	"github.com/status-im/status-go/eth-node/crypto"
    16  	"github.com/status-im/status-go/eth-node/types"
    17  	wallet_common "github.com/status-im/status-go/services/wallet/common"
    18  	"github.com/status-im/status-go/services/wallet/router/pathprocessor"
    19  )
    20  
    21  func rowsToMultiTransactions(rows *sql.Rows) ([]*MultiTransaction, error) {
    22  	var multiTransactions []*MultiTransaction
    23  	for rows.Next() {
    24  		multiTransaction := &MultiTransaction{}
    25  		var fromAmountDB, toAmountDB sql.NullString
    26  		var fromTxHash, toTxHash sql.RawBytes
    27  		err := rows.Scan(
    28  			&multiTransaction.ID,
    29  			&multiTransaction.FromNetworkID,
    30  			&fromTxHash,
    31  			&multiTransaction.FromAddress,
    32  			&multiTransaction.FromAsset,
    33  			&fromAmountDB,
    34  			&multiTransaction.ToNetworkID,
    35  			&toTxHash,
    36  			&multiTransaction.ToAddress,
    37  			&multiTransaction.ToAsset,
    38  			&toAmountDB,
    39  			&multiTransaction.Type,
    40  			&multiTransaction.CrossTxID,
    41  			&multiTransaction.Timestamp,
    42  		)
    43  		if len(fromTxHash) > 0 {
    44  			multiTransaction.FromTxHash = common.BytesToHash(fromTxHash)
    45  		}
    46  		if len(toTxHash) > 0 {
    47  			multiTransaction.ToTxHash = common.BytesToHash(toTxHash)
    48  		}
    49  		if err != nil {
    50  			return nil, err
    51  		}
    52  
    53  		if fromAmountDB.Valid {
    54  			multiTransaction.FromAmount = new(hexutil.Big)
    55  			if _, ok := (*big.Int)(multiTransaction.FromAmount).SetString(fromAmountDB.String, 0); !ok {
    56  				return nil, errors.New("failed to convert fromAmountDB.String to big.Int: " + fromAmountDB.String)
    57  			}
    58  		}
    59  
    60  		if toAmountDB.Valid {
    61  			multiTransaction.ToAmount = new(hexutil.Big)
    62  			if _, ok := (*big.Int)(multiTransaction.ToAmount).SetString(toAmountDB.String, 0); !ok {
    63  				return nil, errors.New("failed to convert fromAmountDB.String to big.Int: " + toAmountDB.String)
    64  			}
    65  		}
    66  
    67  		multiTransactions = append(multiTransactions, multiTransaction)
    68  	}
    69  
    70  	return multiTransactions, nil
    71  }
    72  
    73  func addSignaturesToTransactions(transactions map[common.Hash]*TransactionDescription, signatures map[string]SignatureDetails) error {
    74  	if len(transactions) == 0 {
    75  		return errors.New("no transactions to proceed with")
    76  	}
    77  	if len(signatures) != len(transactions) {
    78  		return errors.New("not all transactions have been signed")
    79  	}
    80  
    81  	// check if all transactions have been signed
    82  	for hash, desc := range transactions {
    83  		sigDetails, ok := signatures[hash.String()]
    84  		if !ok {
    85  			return fmt.Errorf("missing signature for transaction %s", hash)
    86  		}
    87  
    88  		rBytes, _ := hex.DecodeString(sigDetails.R)
    89  		sBytes, _ := hex.DecodeString(sigDetails.S)
    90  		vByte := byte(0)
    91  		if sigDetails.V == "01" {
    92  			vByte = 1
    93  		}
    94  
    95  		desc.signature = make([]byte, crypto.SignatureLength)
    96  		copy(desc.signature[32-len(rBytes):32], rBytes)
    97  		copy(desc.signature[64-len(rBytes):64], sBytes)
    98  		desc.signature[64] = vByte
    99  	}
   100  
   101  	return nil
   102  }
   103  
   104  func multiTransactionFromCommand(command *MultiTransactionCommand) *MultiTransaction {
   105  	toAmount := new(hexutil.Big)
   106  	if command.ToAmount != nil {
   107  		toAmount = command.ToAmount
   108  	}
   109  	multiTransaction := NewMultiTransaction(
   110  		/* Timestamp:     */ uint64(time.Now().Unix()),
   111  		/* FromNetworkID: */ 0,
   112  		/* ToNetworkID:	  */ 0,
   113  		/* FromTxHash:    */ common.Hash{},
   114  		/* ToTxHash:      */ common.Hash{},
   115  		/* FromAddress:   */ command.FromAddress,
   116  		/* ToAddress:     */ command.ToAddress,
   117  		/* FromAsset:     */ command.FromAsset,
   118  		/* ToAsset:       */ command.ToAsset,
   119  		/* FromAmount:    */ command.FromAmount,
   120  		/* ToAmount:      */ toAmount,
   121  		/* Type:		  */ command.Type,
   122  		/* CrossTxID:	  */ "",
   123  	)
   124  
   125  	return multiTransaction
   126  }
   127  
   128  func updateDataFromMultiTx(data []*pathprocessor.MultipathProcessorTxArgs, multiTransaction *MultiTransaction) {
   129  	for _, tx := range data {
   130  		if tx.TransferTx != nil {
   131  			tx.TransferTx.MultiTransactionID = multiTransaction.ID
   132  			tx.TransferTx.Symbol = multiTransaction.FromAsset
   133  		}
   134  		if tx.HopTx != nil {
   135  			tx.HopTx.MultiTransactionID = multiTransaction.ID
   136  			tx.HopTx.Symbol = multiTransaction.FromAsset
   137  		}
   138  		if tx.CbridgeTx != nil {
   139  			tx.CbridgeTx.MultiTransactionID = multiTransaction.ID
   140  			tx.CbridgeTx.Symbol = multiTransaction.FromAsset
   141  		}
   142  		if tx.ERC721TransferTx != nil {
   143  			tx.ERC721TransferTx.MultiTransactionID = multiTransaction.ID
   144  			tx.ERC721TransferTx.Symbol = multiTransaction.FromAsset
   145  		}
   146  		if tx.ERC1155TransferTx != nil {
   147  			tx.ERC1155TransferTx.MultiTransactionID = multiTransaction.ID
   148  			tx.ERC1155TransferTx.Symbol = multiTransaction.FromAsset
   149  		}
   150  		if tx.SwapTx != nil {
   151  			tx.SwapTx.MultiTransactionID = multiTransaction.ID
   152  			tx.SwapTx.Symbol = multiTransaction.FromAsset
   153  		}
   154  	}
   155  }
   156  
   157  func sendTransactions(data []*pathprocessor.MultipathProcessorTxArgs, pathProcessors map[string]pathprocessor.PathProcessor, account *account.SelectedExtKey) (
   158  	map[uint64][]types.Hash, error) {
   159  
   160  	hashes := make(map[uint64][]types.Hash)
   161  	usedNonces := make(map[uint64]int64)
   162  	for _, tx := range data {
   163  
   164  		lastUsedNonce := int64(-1)
   165  		if nonce, ok := usedNonces[tx.ChainID]; ok {
   166  			lastUsedNonce = nonce
   167  		}
   168  
   169  		hash, usedNonce, err := pathProcessors[tx.Name].Send(tx, lastUsedNonce, account)
   170  		if err != nil {
   171  			return nil, err // TODO: One of transfers within transaction could have been sent. Need to notify user about it
   172  		}
   173  
   174  		hashes[tx.ChainID] = append(hashes[tx.ChainID], hash)
   175  		usedNonces[tx.ChainID] = int64(usedNonce)
   176  	}
   177  	return hashes, nil
   178  }
   179  
   180  func idFromTimestamp() wallet_common.MultiTransactionIDType {
   181  	return wallet_common.MultiTransactionIDType(time.Now().UnixMilli())
   182  }
   183  
   184  var multiTransactionIDGenerator func() wallet_common.MultiTransactionIDType = idFromTimestamp
   185  
   186  func (tm *TransactionManager) removeMultiTransactionByAddress(address common.Address) error {
   187  	// We must not remove those transactions, where from_address and to_address are different and both are stored in accounts DB
   188  	// and one of them is equal to the address, as we want to keep the records for the other address
   189  	// That is why we don't use cascade delete here with references to transfers table, as we might have 2 records in multi_transactions
   190  	// for the same transaction, one for each address
   191  
   192  	details := NewMultiTxDetails()
   193  	details.FromAddress = address
   194  	mtxs, err := tm.storage.ReadMultiTransactions(details)
   195  
   196  	ids := make([]wallet_common.MultiTransactionIDType, 0)
   197  	for _, mtx := range mtxs {
   198  		// Remove self transactions as well, leave only those where we have the counterparty in accounts DB
   199  		if mtx.FromAddress != mtx.ToAddress {
   200  			// If both addresses are stored in accounts DB, we don't remove the record
   201  			var addressToCheck common.Address
   202  			if mtx.FromAddress == address {
   203  				addressToCheck = mtx.ToAddress
   204  			} else {
   205  				addressToCheck = mtx.FromAddress
   206  			}
   207  			counterpartyExists, err := tm.accountsDB.AddressExists(types.Address(addressToCheck))
   208  			if err != nil {
   209  				log.Error("Failed to query accounts db for a given address", "address", address, "error", err)
   210  				continue
   211  			}
   212  
   213  			// Skip removal if counterparty is in accounts DB and removed address is not sender
   214  			if counterpartyExists && address != mtx.FromAddress {
   215  				continue
   216  			}
   217  		}
   218  
   219  		ids = append(ids, mtx.ID)
   220  	}
   221  
   222  	if len(ids) > 0 {
   223  		for _, id := range ids {
   224  			err = tm.storage.DeleteMultiTransaction(id)
   225  			if err != nil {
   226  				log.Error("Failed to delete multi transaction", "id", id, "error", err)
   227  			}
   228  		}
   229  	}
   230  
   231  	return err
   232  }