diff --git a/pkg/eventprocessor/impl/webhook.go b/pkg/eventprocessor/impl/webhook.go index 13f83830..1b4e3f7b 100644 --- a/pkg/eventprocessor/impl/webhook.go +++ b/pkg/eventprocessor/impl/webhook.go @@ -8,10 +8,14 @@ import ( "html/template" "net/http" "net/url" + "strings" "time" + "github.com/ethereum/go-ethereum/common" logger "github.com/rs/zerolog/log" + "github.com/textileio/go-tableland/internal/tableland" "github.com/textileio/go-tableland/pkg/eventprocessor" + "github.com/textileio/go-tableland/pkg/tables" ) // webhookTemplate is the template used to generate the webhook content. @@ -19,9 +23,9 @@ const contentTemplate = ` {{ if .Error }} **Error processing Tableland event:** -Chain ID: {{ .ChainID }} +Chain ID: [{{ .ChainID }}]({{ .TBLDocsURL }}) Block number: {{ .BlockNumber }} -Transaction hash: {{ .TxnHash }} +Transaction hash: [{{ .TxnHash }}]({{ .TxnURL }}) Table IDs: {{ .TableIDs }} Error: **{{ .Error }}** Error event index: {{ .ErrorEventIdx }} @@ -29,9 +33,9 @@ Error event index: {{ .ErrorEventIdx }} {{ else }} **Tableland event processed successfully:** -Chain ID: {{ .ChainID }} +Chain ID: [{{ .ChainID }}]({{ .TBLDocsURL }}) Block number: {{ .BlockNumber }} -Transaction hash: {{ .TxnHash }} +Transaction hash: [{{ .TxnHash }}]({{ .TxnURL }}) Table IDs: {{ .TableIDs }} {{ end }} @@ -76,6 +80,131 @@ func sendWebhookRequest(ctx context.Context, url string, body interface{}) error return nil } +type chain struct { + ID int64 + Name string + ContractAddr common.Address + TBLDocsURL string + BlockExplorerURL string + SupportsNFTView bool +} + +var chains = map[tableland.ChainID]chain{ + // Ethereum + 1: { + ContractAddr: common.HexToAddress("0x012969f7e3439a9B04025b5a049EB9BAD82A8C12"), + TBLDocsURL: "https://docs.tableland.xyz/fundamentals/chains/ethereum", + BlockExplorerURL: "https://etherscan.io", + SupportsNFTView: true, + }, + // Optimism + 10: { + ContractAddr: common.HexToAddress("0xfad44BF5B843dE943a09D4f3E84949A11d3aa3e6"), + TBLDocsURL: "https://docs.tableland.xyz/fundamentals/chains/optimism", + BlockExplorerURL: "https://optimistic.etherscan.io", + SupportsNFTView: false, + }, + // Polygon + 137: { + ContractAddr: common.HexToAddress("0x5c4e6A9e5C1e1BF445A062006faF19EA6c49aFeA"), + TBLDocsURL: "https://docs.tableland.xyz/fundamentals/chains/polygon", + BlockExplorerURL: "https://polygonscan.com", + SupportsNFTView: false, + }, + // Arbitrum One + 42161: { + ContractAddr: common.HexToAddress("0x9aBd75E8640871A5a20d3B4eE6330a04c962aFfd"), + TBLDocsURL: "https://docs.tableland.xyz/fundamentals/chains/arbitrum", + BlockExplorerURL: "https://arbiscan.io", + SupportsNFTView: false, + }, + // Arbitrum Nova + 42170: { + ContractAddr: common.HexToAddress("0x1a22854c5b1642760a827f20137a67930ae108d2"), + TBLDocsURL: "https://docs.tableland.xyz/fundamentals/chains/arbitrum", + BlockExplorerURL: "https://nova.arbiscan.io", + SupportsNFTView: false, + }, + // Filecoin + 314: { + ContractAddr: common.HexToAddress("0x59EF8Bf2d6c102B4c42AEf9189e1a9F0ABfD652d"), + TBLDocsURL: "https://docs.tableland.xyz/fundamentals/chains/Filecoin", + BlockExplorerURL: "https://filfox.info/en", + SupportsNFTView: false, + }, + // Goerli + 5: { + ContractAddr: common.HexToAddress("0xDA8EA22d092307874f30A1F277D1388dca0BA97a"), + TBLDocsURL: "https://docs.tableland.xyz/fundamentals/chains/ethereum", + BlockExplorerURL: "https://goerli.etherscan.io", + SupportsNFTView: true, + }, + // Sepolia + 11155111: { + ContractAddr: common.HexToAddress("0xc50C62498448ACc8dBdE43DA77f8D5D2E2c7597D"), + TBLDocsURL: "https://docs.tableland.xyz/fundamentals/chains/ethereum", + BlockExplorerURL: "https://sepolia.etherscan.io", + SupportsNFTView: true, + }, + // Optimism Goerli + 420: { + ContractAddr: common.HexToAddress("0xC72E8a7Be04f2469f8C2dB3F1BdF69A7D516aBbA"), + TBLDocsURL: "https://docs.tableland.xyz/fundamentals/chains/optimism", + BlockExplorerURL: "https://blockscout.com/optimism/goerli", + SupportsNFTView: false, + }, + // Arbitrum Goerli + 421613: { + ContractAddr: common.HexToAddress("0x033f69e8d119205089Ab15D340F5b797732f646b"), + TBLDocsURL: "https://docs.tableland.xyz/fundamentals/chains/arbitrum", + BlockExplorerURL: "https://goerli-rollup-explorer.arbitrum.io", + SupportsNFTView: false, + }, + // Filecoin Calibration + 314159: { + ContractAddr: common.HexToAddress("0x030BCf3D50cad04c2e57391B12740982A9308621"), + TBLDocsURL: "https://docs.tableland.xyz/fundamentals/chains/Filecoin", + BlockExplorerURL: "https://calibration.filfox.info/en", + SupportsNFTView: false, + }, + // Polygon Mumbai + 80001: { + ContractAddr: common.HexToAddress("0x4b48841d4b32C4650E4ABc117A03FE8B51f38F68"), + TBLDocsURL: "https://docs.tableland.xyz/fundamentals/chains/polygon", + BlockExplorerURL: "https://mumbai.polygonscan.com", + SupportsNFTView: false, + }, +} + +type webhookContentData struct { + ChainID tableland.ChainID + TBLDocsURL string + BlockNumber int64 + TxnHash string + TxnURL string + TableIDs string + Error *string + ErrorEventIdx *int +} + +// getTableNFTViews returns the NFT views for the given table IDs. +func getTableNFTViews(tableIDs tables.TableIDs, chainID tableland.ChainID) string { + var tableNFTURLs []string + ch := chains[chainID] + for _, tableID := range tableIDs { + if !ch.SupportsNFTView { + tableNFTURLs = append(tableNFTURLs, tableID.String()) + } else { + blockExplorerURL := ch.BlockExplorerURL + contractAddr := ch.ContractAddr + tableNFTURLs = append(tableNFTURLs, + fmt.Sprintf("[%s](%s/nft/%s/%s)", tableID.String(), + blockExplorerURL, contractAddr, tableID.String())) + } + } + return strings.Join(tableNFTURLs, ", ") +} + // Content function to return the formatted content for the webhook. func content(r eventprocessor.Receipt) (string, error) { var c bytes.Buffer @@ -83,7 +212,19 @@ func content(r eventprocessor.Receipt) (string, error) { if err != nil { return "", fmt.Errorf("failed to parse template: %v", err) } - err = tmpl.Execute(&c, r) + + txnURL := chains[r.ChainID].BlockExplorerURL + "/tx/" + r.TxnHash + docsURL := chains[r.ChainID].TBLDocsURL + err = tmpl.Execute(&c, webhookContentData{ + ChainID: r.ChainID, + TBLDocsURL: docsURL, + BlockNumber: r.BlockNumber, + TxnHash: r.TxnHash, + TxnURL: txnURL, + TableIDs: getTableNFTViews(r.TableIDs, r.ChainID), + Error: r.Error, + ErrorEventIdx: r.ErrorEventIdx, + }) if err != nil { return "", fmt.Errorf("failed to execute template: %v", err) } diff --git a/pkg/eventprocessor/impl/webhook_test.go b/pkg/eventprocessor/impl/webhook_test.go index 17159891..498105d0 100644 --- a/pkg/eventprocessor/impl/webhook_test.go +++ b/pkg/eventprocessor/impl/webhook_test.go @@ -23,6 +23,10 @@ func TestExecuteWebhook(t *testing.T) { mockTableIDs = append(mockTableIDs, tID) } + ethChain := chains[1] + filChain := chains[314] + filCalibChain := chains[314159] + testCases := []struct { name string receipts []eventprocessor.Receipt @@ -41,11 +45,64 @@ func TestExecuteWebhook(t *testing.T) { }, }, expectedOutput: []string{ - "\n\n**Tableland event processed successfully:**\n\n" + - "Chain ID: 1\n" + - "Block number: 1\n" + - "Transaction hash: hash1\n" + - "Table IDs: 0\n\n\n", + fmt.Sprintf( + "\n\n**Tableland event processed successfully:**\n\n"+ + "Chain ID: [1](%s)\n"+ + "Block number: 1\n"+ + "Transaction hash: [hash1](%s)\n"+ + "Table IDs: [0](%s)\n\n\n", + ethChain.TBLDocsURL, + ethChain.BlockExplorerURL+"/tx/hash1", + ethChain.BlockExplorerURL+"/nft/"+ethChain.ContractAddr.String()+"/0", + ), + }, + }, + { + name: "single receipt Filecoin", + receipts: []eventprocessor.Receipt{ + { + BlockNumber: 1, + ChainID: 314, + TxnHash: "hash1", + Error: nil, + ErrorEventIdx: nil, + TableIDs: mockTableIDs[:1], + }, + }, + expectedOutput: []string{ + fmt.Sprintf( + "\n\n**Tableland event processed successfully:**\n\n"+ + "Chain ID: [314](%s)\n"+ + "Block number: 1\n"+ + "Transaction hash: [hash1](%s)\n"+ + "Table IDs: 0\n\n\n", + filChain.TBLDocsURL, + filChain.BlockExplorerURL+"/tx/hash1", + ), + }, + }, + { + name: "single receipt Filecoin Testnet", + receipts: []eventprocessor.Receipt{ + { + BlockNumber: 1, + ChainID: 314159, + TxnHash: "hash1", + Error: nil, + ErrorEventIdx: nil, + TableIDs: mockTableIDs[:1], + }, + }, + expectedOutput: []string{ + fmt.Sprintf( + "\n\n**Tableland event processed successfully:**\n\n"+ + "Chain ID: [314159](%s)\n"+ + "Block number: 1\n"+ + "Transaction hash: [hash1](%s)\n"+ + "Table IDs: 0\n\n\n", + filCalibChain.TBLDocsURL, + filCalibChain.BlockExplorerURL+"/tx/hash1", + ), }, }, { @@ -69,16 +126,29 @@ func TestExecuteWebhook(t *testing.T) { }, }, expectedOutput: []string{ - "\n\n**Tableland event processed successfully:**\n\n" + - "Chain ID: 1\n" + - "Block number: 1\n" + - "Transaction hash: hash1\n" + - "Table IDs: 0,1\n\n\n", - "\n\n**Tableland event processed successfully:**\n\n" + - "Chain ID: 1\n" + - "Block number: 2\n" + - "Transaction hash: hash2\n" + - "Table IDs: 0,1,2\n\n\n", + fmt.Sprintf( + "\n\n**Tableland event processed successfully:**\n\n"+ + "Chain ID: [1](%s)\n"+ + "Block number: 1\n"+ + "Transaction hash: [hash1](%s)\n"+ + "Table IDs: [0](%s), [1](%s)\n\n\n", + ethChain.TBLDocsURL, + ethChain.BlockExplorerURL+"/tx/hash1", + ethChain.BlockExplorerURL+"/nft/"+ethChain.ContractAddr.String()+"/0", + ethChain.BlockExplorerURL+"/nft/"+ethChain.ContractAddr.String()+"/1", + ), + fmt.Sprintf( + "\n\n**Tableland event processed successfully:**\n\n"+ + "Chain ID: [1](%s)\n"+ + "Block number: 2\n"+ + "Transaction hash: [hash2](%s)\n"+ + "Table IDs: [0](%s), [1](%s), [2](%s)\n\n\n", + ethChain.TBLDocsURL, + ethChain.BlockExplorerURL+"/tx/hash2", + ethChain.BlockExplorerURL+"/nft/"+ethChain.ContractAddr.String()+"/0", + ethChain.BlockExplorerURL+"/nft/"+ethChain.ContractAddr.String()+"/1", + ethChain.BlockExplorerURL+"/nft/"+ethChain.ContractAddr.String()+"/2", + ), }, }, { @@ -94,13 +164,18 @@ func TestExecuteWebhook(t *testing.T) { }, }, expectedOutput: []string{ - "\n\n**Error processing Tableland event:**\n\n" + - "Chain ID: 1\n" + - "Block number: 1\n" + - "Transaction hash: hash1\n" + - "Table IDs: 0\n" + - "Error: **error1**\n" + - "Error event index: 1\n\n\n", + fmt.Sprintf( + "\n\n**Error processing Tableland event:**\n\n"+ + "Chain ID: [1](%s)\n"+ + "Block number: 1\n"+ + "Transaction hash: [hash1](%s)\n"+ + "Table IDs: [0](%s)\n"+ + "Error: **error1**\n"+ + "Error event index: 1\n\n\n", + ethChain.TBLDocsURL, + ethChain.BlockExplorerURL+"/tx/hash1", + ethChain.BlockExplorerURL+"/nft/"+ethChain.ContractAddr.String()+"/0", + ), }, }, }