package transfer import ( "context" "encoding/hex" "fmt" "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/status-im/status-go/rpc/chain" w_common "github.com/status-im/status-go/services/wallet/common" "github.com/status-im/status-go/services/wallet/token" ) // TODO: Find proper way to uniquely match Origin and Destination transactions (some sort of hash or uniqueID) // Current approach is not failsafe (for example, if multiple identical bridge operations are triggered // at the same time) // Recipient + Relayer + Data should match in both Origin and Destination transactions func getHopBridgeFromL1CrossTxID(recipient common.Address, relayer common.Address, logData []byte) string { return fmt.Sprintf("FromL1_%s_%s_%s", recipient.String(), relayer.String(), hex.EncodeToString(logData)) } func getHopBridgeFromL2CrossTxID(transferID *big.Int) string { return fmt.Sprintf("FromL2_0x%s", transferID.Text(16)) } type originTxParams struct { fromNetworkID uint64 fromTxHash common.Hash fromAddress common.Address fromAsset string fromAmount *big.Int toNetworkID uint64 toAddress common.Address crossTxID string } func upsertHopBridgeOriginTx(ctx context.Context, transactionManager *TransactionManager, params originTxParams) (*MultiTransaction, error) { // Try to find "destination" half of the multiTx multiTx, err := transactionManager.GetBridgeDestinationMultiTransaction(ctx, params.toNetworkID, params.crossTxID) if err != nil { return nil, err } if multiTx == nil { multiTx = &MultiTransaction{ // Data from "origin" transaction FromNetworkID: params.fromNetworkID, FromTxHash: params.fromTxHash, FromAddress: params.fromAddress, FromAsset: params.fromAsset, FromAmount: (*hexutil.Big)(params.fromAmount), ToNetworkID: params.toNetworkID, ToAddress: params.toAddress, // To be replaced by "destination" transaction, need to be non-null ToAsset: params.fromAsset, ToAmount: (*hexutil.Big)(params.fromAmount), // Common data Type: MultiTransactionBridge, CrossTxID: params.crossTxID, } _, err := transactionManager.InsertMultiTransaction(multiTx) if err != nil { return nil, err } } else { multiTx.FromNetworkID = params.fromNetworkID multiTx.FromTxHash = params.fromTxHash multiTx.FromAddress = params.fromAddress multiTx.FromAsset = params.fromAsset multiTx.FromAmount = (*hexutil.Big)(params.fromAmount) err := transactionManager.UpdateMultiTransaction(multiTx) if err != nil { return nil, err } } return multiTx, nil } type destinationTxParams struct { toNetworkID uint64 toTxHash common.Hash toAddress common.Address toAsset string toAmount *big.Int crossTxID string } func upsertHopBridgeDestinationTx(ctx context.Context, transactionManager *TransactionManager, params destinationTxParams) (*MultiTransaction, error) { // Try to find "origin" half of the multiTx multiTx, err := transactionManager.GetBridgeOriginMultiTransaction(ctx, params.toNetworkID, params.crossTxID) if err != nil { return nil, err } if multiTx == nil { multiTx = &MultiTransaction{ // To be replaced by "origin" transaction, need to be non-null FromAddress: params.toAddress, FromAsset: params.toAsset, FromAmount: (*hexutil.Big)(params.toAmount), // Data from "destination" transaction ToNetworkID: params.toNetworkID, ToTxHash: params.toTxHash, ToAddress: params.toAddress, ToAsset: params.toAsset, ToAmount: (*hexutil.Big)(params.toAmount), // Common data Type: MultiTransactionBridge, CrossTxID: params.crossTxID, } _, err := transactionManager.InsertMultiTransaction(multiTx) if err != nil { return nil, err } } else { multiTx.ToTxHash = params.toTxHash multiTx.ToAsset = params.toAsset multiTx.ToAmount = (*hexutil.Big)(params.toAmount) err := transactionManager.UpdateMultiTransaction(multiTx) if err != nil { return nil, err } } return multiTx, nil } func buildHopBridgeMultitransaction(ctx context.Context, client chain.ClientInterface, transactionManager *TransactionManager, tokenManager *token.Manager, subTx *Transfer) (*MultiTransaction, error) { // Identify if it's from/to transaction switch w_common.GetEventType(subTx.Log) { case w_common.HopBridgeTransferSentToL2EventType: // L1->L2 Origin transaction toChainID, recipient, relayer, fromAmount, err := w_common.ParseHopBridgeTransferSentToL2Log(subTx.Log) if err != nil { return nil, err } params := originTxParams{ fromNetworkID: subTx.NetworkID, fromTxHash: subTx.Receipt.TxHash, fromAddress: subTx.From, fromAsset: "ETH", fromAmount: fromAmount, toNetworkID: toChainID, toAddress: recipient, crossTxID: getHopBridgeFromL1CrossTxID(recipient, relayer, subTx.Log.Data), } return upsertHopBridgeOriginTx(ctx, transactionManager, params) case w_common.HopBridgeTransferFromL1CompletedEventType: // L1->L2 Destination transaction recipient, relayer, toAmount, err := w_common.ParseHopBridgeTransferFromL1CompletedLog(subTx.Log) if err != nil { return nil, err } params := destinationTxParams{ toNetworkID: subTx.NetworkID, toTxHash: subTx.Receipt.TxHash, toAddress: recipient, toAsset: "ETH", toAmount: toAmount, crossTxID: getHopBridgeFromL1CrossTxID(recipient, relayer, subTx.Log.Data), } return upsertHopBridgeDestinationTx(ctx, transactionManager, params) case w_common.HopBridgeTransferSentEventType: // L2->L1 / L2->L2 Origin transaction transferID, toChainID, recipient, fromAmount, _, _, _, _, _, err := w_common.ParseHopBridgeTransferSentLog(subTx.Log) if err != nil { return nil, err } params := originTxParams{ fromNetworkID: subTx.NetworkID, fromTxHash: subTx.Receipt.TxHash, fromAddress: subTx.From, fromAsset: "ETH", fromAmount: fromAmount, toNetworkID: toChainID, toAddress: recipient, crossTxID: getHopBridgeFromL2CrossTxID(transferID), } return upsertHopBridgeOriginTx(ctx, transactionManager, params) case w_common.HopBridgeWithdrawalBondedEventType: // L2->L1 / L2->L2 Destination transaction transferID, toAmount, err := w_common.ParseHopWithdrawalBondedLog(subTx.Log) if err != nil { return nil, err } params := destinationTxParams{ toNetworkID: subTx.NetworkID, toTxHash: subTx.Receipt.TxHash, toAddress: subTx.Address, toAsset: "ETH", toAmount: toAmount, crossTxID: getHopBridgeFromL2CrossTxID(transferID), } return upsertHopBridgeDestinationTx(ctx, transactionManager, params) } return nil, nil }