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 }