github.com/0xsequence/ethkit@v1.25.0/cmd/chain-receipts/main.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "log" 8 "sync" 9 "time" 10 11 "github.com/0xsequence/ethkit" 12 "github.com/0xsequence/ethkit/ethmonitor" 13 "github.com/0xsequence/ethkit/ethreceipts" 14 "github.com/0xsequence/ethkit/ethrpc" 15 "github.com/0xsequence/ethkit/go-ethereum/common" 16 "github.com/0xsequence/ethkit/go-ethereum/core/types" 17 "github.com/0xsequence/ethkit/go-ethereum/crypto" 18 "github.com/0xsequence/ethkit/util" 19 "github.com/goware/logger" 20 ) 21 22 var ETH_NODE_URL = "http://localhost:8545" 23 var ETH_NODE_WSS_URL = "" 24 25 func init() { 26 testConfig, err := util.ReadTestConfig("../../ethkit-test.json") 27 if err != nil { 28 panic(err) 29 } 30 31 if testConfig["POLYGON_MAINNET_URL"] != "" { 32 ETH_NODE_URL = testConfig["POLYGON_MAINNET_URL"] 33 ETH_NODE_WSS_URL = testConfig["POLYGON_MAINNET_WSS_URL"] 34 } 35 // if testConfig["MAINNET_URL"] != "" { 36 // ETH_NODE_URL = testConfig["MAINNET_URL"] 37 // ETH_NODE_WSS_URL = testConfig["MAINNET_WSS_URL"] 38 // } 39 } 40 41 func main() { 42 fmt.Println("chain-receipts start") 43 44 // Provider 45 provider, err := ethrpc.NewProvider(ETH_NODE_URL, ethrpc.WithStreaming(ETH_NODE_WSS_URL)) 46 if err != nil { 47 log.Fatal(err) 48 } 49 50 // Monitor options 51 monitorOptions := ethmonitor.DefaultOptions 52 monitorOptions.PollingInterval = time.Duration(1000 * time.Millisecond) 53 // monitorOptions.DebugLogging = true 54 monitorOptions.WithLogs = true 55 monitorOptions.BlockRetentionLimit = 400 56 monitorOptions.StartBlockNumber = nil // track the head 57 58 receiptListenerOptions := ethreceipts.DefaultOptions 59 receiptListenerOptions.NumBlocksToFinality = 20 60 receiptListenerOptions.FilterMaxWaitNumBlocks = 5 61 62 err = listener(provider, monitorOptions, receiptListenerOptions) 63 if err != nil { 64 log.Fatal(err) 65 } 66 } 67 68 func listener(provider *ethrpc.Provider, monitorOptions ethmonitor.Options, receiptListenerOptions ethreceipts.Options) error { 69 ctx := context.Background() 70 71 monitor, err := ethmonitor.NewMonitor(provider, monitorOptions) 72 if err != nil { 73 log.Fatal(err) 74 } 75 76 go func() { 77 err = monitor.Run(ctx) 78 if err != nil { 79 panic(err) 80 } 81 }() 82 defer monitor.Stop() 83 84 // monitorSub := monitor.Subscribe() 85 // defer monitorSub.Unsubscribe() 86 87 receiptListener, err := ethreceipts.NewReceiptsListener(logger.NewLogger(logger.LogLevel_INFO), provider, monitor, receiptListenerOptions) 88 if err != nil { 89 log.Fatal(err) 90 } 91 92 go func() { 93 err := receiptListener.Run(ctx) 94 if err != nil { 95 log.Fatal(err) 96 } 97 }() 98 defer receiptListener.Stop() 99 100 // Find specific meta transaction -- note: this is not the "transaction hash", 101 // this is a sub-transaction where the id is emitted as an event. 102 FilterMetaTransactionID := func(metaTxnID ethkit.Hash) ethreceipts.FilterQuery { 103 return ethreceipts.FilterLogs(func(logs []*types.Log) bool { 104 for _, log := range logs { 105 isTxExecuted := IsTxExecutedEvent(log, metaTxnID) 106 isTxFailed := IsTxFailedEvent(log, metaTxnID) 107 if isTxExecuted || isTxFailed { 108 // found the sequence meta txn 109 return true 110 } 111 } 112 return false 113 }) 114 } 115 _ = FilterMetaTransactionID 116 117 // Find any Sequence meta txns 118 FilterMetaTransactionAny := func() ethreceipts.FilterQuery { 119 return ethreceipts.FilterLogs(func(logs []*types.Log) bool { 120 foundNonceEvent := false 121 for _, log := range logs { 122 if len(log.Topics) > 0 && log.Topics[0] == NonceChangeEventSig { 123 foundNonceEvent = true 124 break 125 } 126 } 127 if !foundNonceEvent { 128 return false 129 } 130 131 for _, log := range logs { 132 if len(log.Topics) == 1 && log.Topics[0] == TxFailedEventSig { 133 // failed sequence txn 134 return true 135 } else if len(log.Topics) == 0 && len(log.Data) == 32 { 136 // possibly a successful sequence txn -- but not for certain 137 return true 138 } 139 } 140 141 return false 142 }) 143 } 144 _ = FilterMetaTransactionAny 145 146 sub := receiptListener.Subscribe( 147 // FilterMetaTransactionID(common.HexToHash("2d5174e4f5ff20a19c34b63e90818c9ced7854675a679373be92b87f718118d4")).LimitOne(true), 148 FilterMetaTransactionAny().MaxWait(0), // listen on all sequence txns 149 ) 150 151 var wg sync.WaitGroup 152 wg.Add(1) 153 go func() { 154 defer wg.Done() 155 for { 156 select { 157 case receipt := <-sub.TransactionReceipt(): 158 159 fmt.Println("=> sequence txn receipt:", receipt.TransactionHash()) 160 161 // This block of code will search for the same metaTxnID in 10 seconds, 162 // so that we can ensure we can find it easily from our cache. 163 // go func(txn common.Hash, receipt ethreceipts.Receipt) { 164 // time.Sleep(10 * time.Second) 165 166 // var metaTxnID common.Hash 167 // for _, log := range receipt.Logs() { 168 // if len(log.Topics) == 0 && len(log.Data) == 32 { 169 // metaTxnID = common.BytesToHash(log.Data) 170 // } 171 // } 172 // fmt.Println("-> search for metaTxnID", metaTxnID) 173 174 // r, _, err := receiptListener.FetchTransactionReceiptWithFilter(context.Background(), FilterMetaTransactionID(metaTxnID).LimitOne(true).SearchCache(true)) 175 // if err != nil { 176 // panic(err) 177 // } 178 // fmt.Println("===> found the meta txn! txnHash:", r.TransactionHash()) 179 // }(receipt.TransactionHash(), receipt) 180 181 case <-sub.Done(): 182 return 183 } 184 } 185 }() 186 187 wg.Wait() 188 189 return nil 190 } 191 192 // Transaction events as defined in wallet-contracts IModuleCalls.sol 193 var ( 194 // NonceChangeEventSig is the signature event emitted as the first event on the batch execution 195 // 0x1f180c27086c7a39ea2a7b25239d1ab92348f07ca7bb59d1438fcf527568f881 196 NonceChangeEventSig = MustEncodeSig("NonceChange(uint256,uint256)") 197 198 // TxFailedEventSig is the signature event emitted in a failed smart-wallet meta-transaction batch 199 // 0x3dbd1590ea96dd3253a91f24e64e3a502e1225d602a5731357bc12643070ccd7 200 TxFailedEventSig = MustEncodeSig("TxFailed(bytes32,bytes)") 201 202 // TxExecutedEventSig is the signature of the event emitted in a successful transaction 203 // 0x0639b0b186d373976f8bb98f9f7226ba8070f10cb6c7f9bd5086d3933f169a25 204 TxExecutedEventSig = MustEncodeSig("TxExecuted(bytes32)") 205 ) 206 207 func MustEncodeSig(str string) common.Hash { 208 return crypto.Keccak256Hash([]byte(str)) 209 } 210 211 func IsTxExecutedEvent(log *types.Log, hash common.Hash) bool { 212 return len(log.Topics) == 0 && 213 len(log.Data) == 32 && 214 bytes.Equal(log.Data, hash[:]) 215 } 216 217 func IsTxFailedEvent(log *types.Log, hash common.Hash) bool { 218 return len(log.Topics) == 1 && 219 log.Topics[0] == TxFailedEventSig && 220 bytes.HasPrefix(log.Data, hash[:]) 221 }