github.com/status-im/status-go@v1.1.0/services/wallet/transfer/transaction_manager_multitransaction.go (about) 1 package transfer 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "time" 8 9 "github.com/pkg/errors" 10 11 "github.com/ethereum/go-ethereum/common" 12 "github.com/ethereum/go-ethereum/log" 13 "github.com/status-im/status-go/account" 14 "github.com/status-im/status-go/eth-node/types" 15 wallet_common "github.com/status-im/status-go/services/wallet/common" 16 "github.com/status-im/status-go/services/wallet/router/pathprocessor" 17 "github.com/status-im/status-go/services/wallet/walletevent" 18 "github.com/status-im/status-go/signal" 19 "github.com/status-im/status-go/transactions" 20 ) 21 22 const multiTransactionColumns = "id, from_network_id, from_tx_hash, from_address, from_asset, from_amount, to_network_id, to_tx_hash, to_address, to_asset, to_amount, type, cross_tx_id, timestamp" 23 const selectMultiTransactionColumns = "id, COALESCE(from_network_id, 0), from_tx_hash, from_address, from_asset, from_amount, COALESCE(to_network_id, 0), to_tx_hash, to_address, to_asset, to_amount, type, cross_tx_id, timestamp" 24 25 var pendingTxTimeout time.Duration = 10 * time.Minute 26 var ErrWatchPendingTxTimeout = errors.New("timeout watching for pending transaction") 27 var ErrPendingTxNotExists = errors.New("pending transaction does not exist") 28 29 func (tm *TransactionManager) InsertMultiTransaction(multiTransaction *MultiTransaction) (wallet_common.MultiTransactionIDType, error) { 30 return multiTransaction.ID, tm.storage.CreateMultiTransaction(multiTransaction) 31 } 32 33 func (tm *TransactionManager) UpdateMultiTransaction(multiTransaction *MultiTransaction) error { 34 return tm.storage.UpdateMultiTransaction(multiTransaction) 35 } 36 37 func (tm *TransactionManager) CreateMultiTransactionFromCommand(command *MultiTransactionCommand, 38 data []*pathprocessor.MultipathProcessorTxArgs) (*MultiTransaction, error) { 39 40 multiTransaction := multiTransactionFromCommand(command) 41 42 // Extract network from args 43 switch multiTransaction.Type { 44 case MultiTransactionSend, MultiTransactionApprove, MultiTransactionSwap: 45 if multiTransaction.FromNetworkID == wallet_common.UnknownChainID && len(data) == 1 { 46 multiTransaction.FromNetworkID = data[0].ChainID 47 } 48 case MultiTransactionBridge: 49 if len(data) == 1 && data[0].HopTx != nil { 50 if multiTransaction.FromNetworkID == wallet_common.UnknownChainID { 51 multiTransaction.FromNetworkID = data[0].HopTx.ChainID 52 } 53 if multiTransaction.ToNetworkID == wallet_common.UnknownChainID { 54 multiTransaction.ToNetworkID = data[0].HopTx.ChainIDTo 55 } 56 } 57 default: 58 return nil, fmt.Errorf("unsupported multi transaction type: %v", multiTransaction.Type) 59 } 60 61 return multiTransaction, nil 62 } 63 64 func (tm *TransactionManager) SendTransactionForSigningToKeycard(ctx context.Context, multiTransaction *MultiTransaction, data []*pathprocessor.MultipathProcessorTxArgs, pathProcessors map[string]pathprocessor.PathProcessor) error { 65 acc, err := tm.accountsDB.GetAccountByAddress(types.Address(multiTransaction.FromAddress)) 66 if err != nil { 67 return err 68 } 69 70 kp, err := tm.accountsDB.GetKeypairByKeyUID(acc.KeyUID) 71 if err != nil { 72 return err 73 } 74 75 if !kp.MigratedToKeycard() { 76 return fmt.Errorf("account being used is not migrated to a keycard, password is required") 77 } 78 79 tm.multiTransactionForKeycardSigning = multiTransaction 80 tm.multipathTransactionsData = data 81 hashes, err := tm.buildTransactions(pathProcessors) 82 if err != nil { 83 return err 84 } 85 86 signal.SendWalletEvent(signal.SignTransactions, hashes) 87 88 return nil 89 } 90 91 func (tm *TransactionManager) SendTransactions(ctx context.Context, multiTransaction *MultiTransaction, data []*pathprocessor.MultipathProcessorTxArgs, pathProcessors map[string]pathprocessor.PathProcessor, account *account.SelectedExtKey) (*MultiTransactionCommandResult, error) { 92 updateDataFromMultiTx(data, multiTransaction) 93 hashes, err := sendTransactions(data, pathProcessors, account) 94 if err != nil { 95 return nil, err 96 } 97 98 return &MultiTransactionCommandResult{ 99 ID: int64(multiTransaction.ID), 100 Hashes: hashes, 101 }, nil 102 } 103 104 func (tm *TransactionManager) ProceedWithTransactionsSignatures(ctx context.Context, signatures map[string]SignatureDetails) (*MultiTransactionCommandResult, error) { 105 if err := addSignaturesToTransactions(tm.transactionsForKeycardSigning, signatures); err != nil { 106 return nil, err 107 } 108 109 // send transactions 110 hashes := make(map[uint64][]types.Hash) 111 for _, desc := range tm.transactionsForKeycardSigning { 112 txWithSignature, err := tm.transactor.AddSignatureToTransaction(desc.chainID, desc.builtTx, desc.signature) 113 if err != nil { 114 return nil, err 115 } 116 117 hash, err := tm.transactor.SendTransactionWithSignature(desc.from, tm.multiTransactionForKeycardSigning.FromAsset, tm.multiTransactionForKeycardSigning.ID, txWithSignature) 118 if err != nil { 119 return nil, err // TODO: One of transfers within transaction could have been sent. Need to notify user about it 120 } 121 hashes[desc.chainID] = append(hashes[desc.chainID], hash) 122 } 123 124 _, err := tm.InsertMultiTransaction(tm.multiTransactionForKeycardSigning) 125 if err != nil { 126 log.Error("failed to insert multi transaction", "err", err) 127 } 128 129 return &MultiTransactionCommandResult{ 130 ID: int64(tm.multiTransactionForKeycardSigning.ID), 131 Hashes: hashes, 132 }, nil 133 } 134 135 func (tm *TransactionManager) GetMultiTransactions(ctx context.Context, ids []wallet_common.MultiTransactionIDType) ([]*MultiTransaction, error) { 136 return tm.storage.ReadMultiTransactions(&MultiTxDetails{IDs: ids}) 137 } 138 139 func (tm *TransactionManager) GetBridgeOriginMultiTransaction(ctx context.Context, toChainID uint64, crossTxID string) (*MultiTransaction, error) { 140 details := NewMultiTxDetails() 141 details.ToChainID = toChainID 142 details.CrossTxID = crossTxID 143 144 multiTxs, err := tm.storage.ReadMultiTransactions(details) 145 if err != nil { 146 return nil, err 147 } 148 149 for _, multiTx := range multiTxs { 150 // Origin MultiTxs will have a missing "ToTxHash" 151 if multiTx.ToTxHash == emptyHash { 152 return multiTx, nil 153 } 154 } 155 156 return nil, nil 157 } 158 159 func (tm *TransactionManager) GetBridgeDestinationMultiTransaction(ctx context.Context, toChainID uint64, crossTxID string) (*MultiTransaction, error) { 160 details := NewMultiTxDetails() 161 details.ToChainID = toChainID 162 details.CrossTxID = crossTxID 163 164 multiTxs, err := tm.storage.ReadMultiTransactions(details) 165 if err != nil { 166 return nil, err 167 } 168 169 for _, multiTx := range multiTxs { 170 // Destination MultiTxs will have a missing "FromTxHash" 171 if multiTx.FromTxHash == emptyHash { 172 return multiTx, nil 173 } 174 } 175 176 return nil, nil 177 } 178 179 func (tm *TransactionManager) WatchTransaction(ctx context.Context, chainID uint64, transactionHash common.Hash) error { 180 // Workaround to keep the blocking call until the clients use the PendingTxTracker APIs 181 eventChan := make(chan walletevent.Event, 2) 182 sub := tm.eventFeed.Subscribe(eventChan) 183 defer sub.Unsubscribe() 184 185 status, err := tm.pendingTracker.Watch(ctx, wallet_common.ChainID(chainID), transactionHash) 186 if err == nil && *status != transactions.Pending { 187 log.Error("transaction is not pending", "status", status) 188 return nil 189 } 190 191 for { 192 select { 193 case we := <-eventChan: 194 if transactions.EventPendingTransactionStatusChanged == we.Type { 195 var p transactions.StatusChangedPayload 196 err = json.Unmarshal([]byte(we.Message), &p) 197 if err != nil { 198 return err 199 } 200 if p.ChainID == wallet_common.ChainID(chainID) && p.Hash == transactionHash { 201 return nil 202 } 203 } 204 case <-time.After(pendingTxTimeout): 205 return ErrWatchPendingTxTimeout 206 } 207 } 208 }