diff --git a/README.md b/README.md new file mode 100644 index 0000000..a6ba65d --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# Trailblazer Adapters + +## Adding a New Protocol Adapter + +### 1. Add your project under adapters/projects folder + +### 2. Write an indexer in the new folder + +An example adapter for tracking OrderFulfilled Event on the OKX marketplace can be seen [here](./adapters/projects/okx/order_fulfilled.go) + +### 3. Test out the adapter by adding it to the cli + +An example on how to add a new adapter to the cli is [here](./cmd/README.md) + +### 4. Create a Pull Request + +Create a pull request (PR) on GitHub to merge your changes into the main branch. Provide a clear description of the changes and the protocol added. + +### 5. Review and Merge + +Wait for the PR to be reviewed by the maintainers. Once approved, your changes will be merged, and the protocol info will be added to trailblazers. + +For further details, refer to the official documentation or contact the maintainers for support. diff --git a/adapters/blocks.go b/adapters/blocks.go index 93951bd..892ce46 100644 --- a/adapters/blocks.go +++ b/adapters/blocks.go @@ -14,5 +14,11 @@ var ( // BlockProcessor is an interface that defines the methods for processing blocks. type BlockProcessor interface { - ProcessBlock(ctx context.Context, block *types.Block, client *ethclient.Client) (*[]common.Address, error) + ProcessBlock(ctx context.Context, block *types.Block, client *ethclient.Client) ([]Whitelist, error) +} + +type Whitelist struct { + User common.Address + Time uint64 + BlockNumber uint64 } diff --git a/adapters/blocks/nft_deployed.go b/adapters/blocks/nft_deployed.go new file mode 100644 index 0000000..a040a7a --- /dev/null +++ b/adapters/blocks/nft_deployed.go @@ -0,0 +1,112 @@ +package blocks + +import ( + "context" + "fmt" + "strings" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/taikoxyz/trailblazer-adapters/adapters" + "github.com/taikoxyz/trailblazer-adapters/adapters/contracts/erc165" +) + +type NftDeployedIndexer struct{} + +// NewNftDeployedIndexer creates a new NftDeployedIndexer. +func NewNftDeployedIndexer() *NftDeployedIndexer { + return &NftDeployedIndexer{} +} + +const ( + ERC721InterfaceID = "0x80ac58cd" // ERC721 interface ID + ERC1155InterfaceID = "0xd9b67a26" // ERC1155 interface ID +) + +func supportsInterface(contractAddress common.Address, client *ethclient.Client, interfaceID string) (bool, error) { + parsedABI, err := abi.JSON(strings.NewReader(erc165.ABI)) + if err != nil { + return false, err + } + + interfaceIDBytes := common.FromHex(interfaceID) + if len(interfaceIDBytes) != 4 { + return false, fmt.Errorf("invalid interface ID length") + } + + data, err := parsedABI.Pack("supportsInterface", [4]byte{interfaceIDBytes[0], interfaceIDBytes[1], interfaceIDBytes[2], interfaceIDBytes[3]}) + if err != nil { + return false, err + } + + msg := ethereum.CallMsg{ + To: &contractAddress, + Data: data, + } + + result, err := client.CallContract(context.Background(), msg, nil) + if err != nil { + return false, err + } + + var supports bool + err = parsedABI.UnpackIntoInterface(&supports, "supportsInterface", result) + if err != nil { + return false, err + } + + return supports, nil +} + +func (indexer *NftDeployedIndexer) ProcessBlock(ctx context.Context, block *types.Block, client *ethclient.Client) ([]adapters.Whitelist, error) { + txs := block.Transactions() + var result []adapters.Whitelist + + for _, tx := range txs { + receipt, err := client.TransactionReceipt(ctx, tx.Hash()) + if err != nil { + return nil, err + } + + sender, err := client.TransactionSender(ctx, tx, block.Hash(), receipt.TransactionIndex) + if err != nil { + return nil, err + } + + to := tx.To() + if to != nil { + continue + } + + isErc721, err := supportsInterface(receipt.ContractAddress, client, ERC721InterfaceID) + if err != nil { + return nil, err + } + + if isErc721 { + result = append(result, adapters.Whitelist{ + User: sender, + BlockNumber: block.Number().Uint64(), + Time: block.Time(), + }) + } + + isErc1155, err := supportsInterface(receipt.ContractAddress, client, ERC1155InterfaceID) + if err != nil { + return nil, err + } + + if isErc1155 { + result = append(result, adapters.Whitelist{ + User: sender, + BlockNumber: block.Number().Uint64(), + Time: block.Time(), + }) + } + } + + return result, nil +} diff --git a/adapters/blocks/transaction_sender.go b/adapters/blocks/transaction_sender.go index 6515d24..85b1eb5 100644 --- a/adapters/blocks/transaction_sender.go +++ b/adapters/blocks/transaction_sender.go @@ -9,8 +9,6 @@ import ( "github.com/taikoxyz/trailblazer-adapters/adapters" ) -var _ adapters.BlockProcessor = (*TransactionSender)(nil) - type TransactionSender struct { ValidRecipient map[string]struct{} } @@ -22,9 +20,9 @@ func NewTransactionSender() *TransactionSender { }} } -func (indexer *TransactionSender) ProcessBlock(ctx context.Context, block *types.Block, client *ethclient.Client) (*[]common.Address, error) { +func (indexer *TransactionSender) ProcessBlock(ctx context.Context, block *types.Block, client *ethclient.Client) ([]adapters.Whitelist, error) { txs := block.Transactions() - var result []common.Address + var result []adapters.Whitelist for _, tx := range txs { receipt, err := client.TransactionReceipt(ctx, tx.Hash()) @@ -40,8 +38,12 @@ func (indexer *TransactionSender) ProcessBlock(ctx context.Context, block *types continue } if _, exists := indexer.ValidRecipient[to.Hex()]; exists { - result = append(result, sender) + result = append(result, adapters.Whitelist{ + User: sender, + BlockNumber: block.Number().Uint64(), + Time: block.Time(), + }) } } - return &result, nil + return result, nil } diff --git a/adapters/contracts/conft/abi.go b/adapters/contracts/conft/abi.go new file mode 100644 index 0000000..2d18b7b --- /dev/null +++ b/adapters/contracts/conft/abi.go @@ -0,0 +1,49 @@ +package conft + +var ( + ABI = `[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint128", + "name": "id", + "type": "uint128" + }, + { + "indexed": true, + "internalType": "address", + "name": "seller", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "buyer", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "contractAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "price", + "type": "uint128" + } + ], + "name": "TokenSold", + "type": "event" + } +]` +) diff --git a/adapters/contracts/erc165/abi.go b/adapters/contracts/erc165/abi.go new file mode 100644 index 0000000..19162fc --- /dev/null +++ b/adapters/contracts/erc165/abi.go @@ -0,0 +1,25 @@ +package erc165 + +var ( + ABI = `[ + { + "constant": true, + "inputs": [ + { + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } + ]` +) diff --git a/adapters/logs.go b/adapters/logs.go index 4ccb7de..cbac90e 100644 --- a/adapters/logs.go +++ b/adapters/logs.go @@ -12,15 +12,6 @@ import ( // TransferLogsIndexer is an interface that defines the methods for indexing and processing transfer logs. type TransferLogsIndexer interface { Addresses() []common.Address - IndexLogs(ctx context.Context, chainID *big.Int, client *ethclient.Client, logs []types.Log) ([]TransferData, error) - ProcessLog(ctx context.Context, chainID *big.Int, client *ethclient.Client, vLog types.Log) (*TransferData, error) -} - -// TransferData represents an event with specific fields. -type TransferData struct { - From common.Address - To common.Address - Value *big.Int - Time uint64 - BlockNumber uint64 + IndexLogs(ctx context.Context, chainID *big.Int, client *ethclient.Client, logs []types.Log) ([]Whitelist, error) + ProcessLog(ctx context.Context, chainID *big.Int, client *ethclient.Client, vLog types.Log) (*Whitelist, error) } diff --git a/adapters/logs/transfer_event.go b/adapters/logs/transfer_event.go index e114911..552c216 100644 --- a/adapters/logs/transfer_event.go +++ b/adapters/logs/transfer_event.go @@ -33,8 +33,8 @@ func (indexer *TransferIndexer) Addresses() []common.Address { } // IndexLogs processes logs for ERC20 transfers. -func (indexer *TransferIndexer) IndexLogs(ctx context.Context, chainID *big.Int, client *ethclient.Client, logs []types.Log) ([]adapters.TransferData, error) { - var result []adapters.TransferData +func (indexer *TransferIndexer) IndexLogs(ctx context.Context, chainID *big.Int, client *ethclient.Client, logs []types.Log) ([]adapters.Whitelist, error) { + var result []adapters.Whitelist for _, vLog := range logs { if !isERC20Transfer(vLog) { continue @@ -53,7 +53,7 @@ func isERC20Transfer(vLog types.Log) bool { } // processLog processes a single ERC20 transfer log. -func (indexer *TransferIndexer) ProcessLog(ctx context.Context, chainID *big.Int, client *ethclient.Client, vLog types.Log) (*adapters.TransferData, error) { +func (indexer *TransferIndexer) ProcessLog(ctx context.Context, chainID *big.Int, client *ethclient.Client, vLog types.Log) (*adapters.Whitelist, error) { to := common.BytesToAddress(vLog.Topics[2].Bytes()[12:]) from := common.BytesToAddress(vLog.Topics[1].Bytes()[12:]) @@ -75,9 +75,9 @@ func (indexer *TransferIndexer) ProcessLog(ctx context.Context, chainID *big.Int return nil, err } - return &adapters.TransferData{ + return &adapters.Whitelist{ From: from, - To: to, + User: to, Time: block.Time(), Value: transferEvent.Value, }, nil diff --git a/adapters/projects/conft/token_sold.go b/adapters/projects/conft/token_sold.go new file mode 100644 index 0000000..8821316 --- /dev/null +++ b/adapters/projects/conft/token_sold.go @@ -0,0 +1,85 @@ +package conft + +import ( + "context" + "math/big" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/taikoxyz/trailblazer-adapters/adapters" + "github.com/taikoxyz/trailblazer-adapters/adapters/contracts/conft" +) + +var ( + logTokenSoldSigHash = crypto.Keccak256Hash([]byte("TokenSold(uint128,address,address,address,uint256,uint128)")) +) + +type TokenSoldEvent struct { + ID *big.Int + Seller common.Address + Buyer common.Address + ContractAddress common.Address + TokenID *big.Int + Price *big.Int +} + +type TokenSoldIndexer struct { + TargetAddresses []common.Address +} + +// NewTokenSoldIndexer creates a new TokenSoldIndexer. +func NewTokenSoldIndexer() *TokenSoldIndexer { + return &TokenSoldIndexer{TargetAddresses: []common.Address{common.HexToAddress("0x6Ce2CFD7674cf47A851690a11d1DB45c6cCBe17A")}} +} + +func (indexer *TokenSoldIndexer) Addresses() []common.Address { + return indexer.TargetAddresses +} + +func (indexer *TokenSoldIndexer) IndexLogs(ctx context.Context, chainID *big.Int, client *ethclient.Client, logs []types.Log) ([]adapters.Whitelist, error) { + var result []adapters.Whitelist + for _, vLog := range logs { + if !indexer.isRelevantLog(vLog.Topics[0]) { + continue + } + transferData, err := indexer.ProcessLog(ctx, chainID, client, vLog) + if err != nil { + return nil, err + } + result = append(result, *transferData) + } + return result, nil +} + +func (indexer *TokenSoldIndexer) isRelevantLog(topic common.Hash) bool { + return topic.Hex() == logTokenSoldSigHash.Hex() +} + +func (indexer *TokenSoldIndexer) ProcessLog(ctx context.Context, chainID *big.Int, client *ethclient.Client, vLog types.Log) (*adapters.Whitelist, error) { + var tokenSoldEvent TokenSoldEvent + + tokenSoldABI, err := abi.JSON(strings.NewReader(conft.ABI)) + if err != nil { + return nil, err + } + + err = tokenSoldABI.UnpackIntoInterface(&tokenSoldEvent, "OrderFulfilled", vLog.Data) + if err != nil { + return nil, err + } + + block, err := client.BlockByNumber(ctx, big.NewInt(int64(vLog.BlockNumber))) + if err != nil { + return nil, err + } + + return &adapters.Whitelist{ + User: tokenSoldEvent.Buyer, + Time: block.Time(), + BlockNumber: block.Number().Uint64(), + }, nil +} diff --git a/adapters/logs/dot_taiko_register.go b/adapters/projects/domains/dot_taiko_register.go similarity index 92% rename from adapters/logs/dot_taiko_register.go rename to adapters/projects/domains/dot_taiko_register.go index ef041b9..96debcd 100644 --- a/adapters/logs/dot_taiko_register.go +++ b/adapters/projects/domains/dot_taiko_register.go @@ -1,4 +1,4 @@ -package logs +package domains import ( "context" @@ -33,8 +33,8 @@ func (indexer *DotTaikoIndexer) Addresses() []common.Address { return indexer.TargetAddresses } -func (indexer *DotTaikoIndexer) IndexLogs(ctx context.Context, chainID *big.Int, client *ethclient.Client, logs []types.Log) ([]adapters.TransferData, error) { - var result []adapters.TransferData +func (indexer *DotTaikoIndexer) IndexLogs(ctx context.Context, chainID *big.Int, client *ethclient.Client, logs []types.Log) ([]adapters.Whitelist, error) { + var result []adapters.Whitelist for _, vLog := range logs { if !indexer.isRelevantLog(vLog.Topics[0]) { continue @@ -52,7 +52,7 @@ func (indexer *DotTaikoIndexer) isRelevantLog(topic common.Hash) bool { return topic.Hex() == logNameRegisteredSigHash.Hex() || topic.Hex() == logMintedDomainSigHash.Hex() || topic.Hex() == logProfileCreatedSigHash.Hex() } -func (indexer *DotTaikoIndexer) ProcessLog(ctx context.Context, chainID *big.Int, client *ethclient.Client, vLog types.Log) (*adapters.TransferData, error) { +func (indexer *DotTaikoIndexer) ProcessLog(ctx context.Context, chainID *big.Int, client *ethclient.Client, vLog types.Log) (*adapters.Whitelist, error) { ownerHex := common.BytesToAddress(vLog.Topics[2].Bytes()[12:]) block, err := client.BlockByNumber(ctx, big.NewInt(int64(vLog.BlockNumber))) @@ -60,8 +60,8 @@ func (indexer *DotTaikoIndexer) ProcessLog(ctx context.Context, chainID *big.Int return nil, err } - return &adapters.TransferData{ - To: ownerHex, + return &adapters.Whitelist{ + User: ownerHex, Time: block.Time(), BlockNumber: block.Number().Uint64(), }, nil diff --git a/adapters/logs/new_sale.go b/adapters/projects/loopex/new_sale.go similarity index 92% rename from adapters/logs/new_sale.go rename to adapters/projects/loopex/new_sale.go index 5a94834..8dad44f 100644 --- a/adapters/logs/new_sale.go +++ b/adapters/projects/loopex/new_sale.go @@ -1,4 +1,4 @@ -package logs +package loopex import ( "context" @@ -43,8 +43,8 @@ func (indexer *NewSaleIndexer) Addresses() []common.Address { return indexer.TargetAddresses } -func (indexer *NewSaleIndexer) IndexLogs(ctx context.Context, chainID *big.Int, client *ethclient.Client, logs []types.Log) ([]adapters.TransferData, error) { - var result []adapters.TransferData +func (indexer *NewSaleIndexer) IndexLogs(ctx context.Context, chainID *big.Int, client *ethclient.Client, logs []types.Log) ([]adapters.Whitelist, error) { + var result []adapters.Whitelist for _, vLog := range logs { if !indexer.isRelevantLog(vLog.Topics[0]) { continue @@ -62,7 +62,7 @@ func (indexer *NewSaleIndexer) isRelevantLog(topic common.Hash) bool { return topic.Hex() == logNewSaleSigHash.Hex() } -func (indexer *NewSaleIndexer) ProcessLog(ctx context.Context, chainID *big.Int, client *ethclient.Client, vLog types.Log) (*adapters.TransferData, error) { +func (indexer *NewSaleIndexer) ProcessLog(ctx context.Context, chainID *big.Int, client *ethclient.Client, vLog types.Log) (*adapters.Whitelist, error) { var newSaleEvent NewSaleEvent newSaleABI, err := abi.JSON(strings.NewReader(sale.ABI)) @@ -80,8 +80,8 @@ func (indexer *NewSaleIndexer) ProcessLog(ctx context.Context, chainID *big.Int, return nil, err } - return &adapters.TransferData{ - To: newSaleEvent.Buyer, + return &adapters.Whitelist{ + User: newSaleEvent.Buyer, Time: block.Time(), BlockNumber: block.Number().Uint64(), }, nil diff --git a/adapters/projects/nfts2me/collection_created.go b/adapters/projects/nfts2me/collection_created.go new file mode 100644 index 0000000..586dd1c --- /dev/null +++ b/adapters/projects/nfts2me/collection_created.go @@ -0,0 +1,69 @@ +package nfts2me + +import ( + "context" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/taikoxyz/trailblazer-adapters/adapters" +) + +var ( + logTransferSigHash = crypto.Keccak256Hash([]byte("Transfer(address,address,uint256)")) +) + +type CollectionCreatedIndexer struct { + TargetAddresses []common.Address +} + +// NewCollectionCreatedIndexer creates a new CollectionCreatedIndexer. +func NewCollectionCreatedIndexer() *CollectionCreatedIndexer { + return &CollectionCreatedIndexer{TargetAddresses: []common.Address{common.HexToAddress("0x00000000001594C61dD8a6804da9AB58eD2483ce")}} +} + +func (indexer *CollectionCreatedIndexer) Addresses() []common.Address { + return indexer.TargetAddresses +} + +func (indexer *CollectionCreatedIndexer) IndexLogs(ctx context.Context, chainID *big.Int, client *ethclient.Client, logs []types.Log) ([]adapters.Whitelist, error) { + var result []adapters.Whitelist + for _, vLog := range logs { + if !isERC721Transfer(vLog) && !isFromZeroAddress(vLog) { + continue + } + transferData, err := indexer.ProcessLog(ctx, chainID, client, vLog) + if err != nil { + return nil, err + } + result = append(result, *transferData) + } + return result, nil +} + +func isERC721Transfer(vLog types.Log) bool { + return len(vLog.Topics) == 4 && vLog.Topics[0].Hex() == logTransferSigHash.Hex() +} + +func isFromZeroAddress(vLog types.Log) bool { + from := common.BytesToAddress(vLog.Topics[1].Bytes()[12:]) + return from.Hex() != adapters.ZeroAddress.Hex() +} + +// processLog processes a single ERC20 transfer log. +func (indexer *CollectionCreatedIndexer) ProcessLog(ctx context.Context, chainID *big.Int, client *ethclient.Client, vLog types.Log) (*adapters.Whitelist, error) { + to := common.BytesToAddress(vLog.Topics[2].Bytes()[12:]) + + block, err := client.BlockByNumber(ctx, big.NewInt(int64(vLog.BlockNumber))) + if err != nil { + return nil, err + } + + return &adapters.Whitelist{ + User: to, + Time: block.Time(), + BlockNumber: block.Number().Uint64(), + }, nil +} diff --git a/adapters/logs/order_fulfilled.go b/adapters/projects/okx/order_fulfilled.go similarity index 92% rename from adapters/logs/order_fulfilled.go rename to adapters/projects/okx/order_fulfilled.go index b4be545..dbfc79e 100644 --- a/adapters/logs/order_fulfilled.go +++ b/adapters/projects/okx/order_fulfilled.go @@ -1,4 +1,4 @@ -package logs +package okx import ( "context" @@ -56,8 +56,8 @@ func (indexer *OrderFulfilledIndexer) Addresses() []common.Address { return indexer.TargetAddresses } -func (indexer *OrderFulfilledIndexer) IndexLogs(ctx context.Context, chainID *big.Int, client *ethclient.Client, logs []types.Log) ([]adapters.TransferData, error) { - var result []adapters.TransferData +func (indexer *OrderFulfilledIndexer) IndexLogs(ctx context.Context, chainID *big.Int, client *ethclient.Client, logs []types.Log) ([]adapters.Whitelist, error) { + var result []adapters.Whitelist for _, vLog := range logs { if !indexer.isRelevantLog(vLog.Topics[0]) { continue @@ -75,7 +75,7 @@ func (indexer *OrderFulfilledIndexer) isRelevantLog(topic common.Hash) bool { return topic.Hex() == logOrderFulfilledSigHash.Hex() } -func (indexer *OrderFulfilledIndexer) ProcessLog(ctx context.Context, chainID *big.Int, client *ethclient.Client, vLog types.Log) (*adapters.TransferData, error) { +func (indexer *OrderFulfilledIndexer) ProcessLog(ctx context.Context, chainID *big.Int, client *ethclient.Client, vLog types.Log) (*adapters.Whitelist, error) { var orderFulfilledEvent OrderFulfilledEvent orderFulfilledABI, err := abi.JSON(strings.NewReader(order.ABI)) @@ -92,8 +92,8 @@ func (indexer *OrderFulfilledIndexer) ProcessLog(ctx context.Context, chainID *b return nil, err } - return &adapters.TransferData{ - To: orderFulfilledEvent.Recipient, + return &adapters.Whitelist{ + User: orderFulfilledEvent.Recipient, Time: block.Time(), BlockNumber: block.Number().Uint64(), }, nil diff --git a/adapters/projects/omnihub/contract_deployed.go b/adapters/projects/omnihub/contract_deployed.go new file mode 100644 index 0000000..2574874 --- /dev/null +++ b/adapters/projects/omnihub/contract_deployed.go @@ -0,0 +1,72 @@ +package omnihub + +import ( + "context" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/taikoxyz/trailblazer-adapters/adapters" +) + +var ( + logContractDeployedSigHash = crypto.Keccak256Hash([]byte("ContractDeployed(address)")) +) + +type ContractDeployedIndexer struct { + TargetAddresses []common.Address +} + +func NewContractDeployedIndexer() *ContractDeployedIndexer { + return &ContractDeployedIndexer{TargetAddresses: []common.Address{ + common.HexToAddress("0xb0B4B761C9e9Bf5A9194a42e944A4A6646B83919"), + }} +} + +func (indexer *ContractDeployedIndexer) Addresses() []common.Address { + return indexer.TargetAddresses +} + +func (indexer *ContractDeployedIndexer) IndexLogs(ctx context.Context, chainID *big.Int, client *ethclient.Client, logs []types.Log) ([]adapters.Whitelist, error) { + var result []adapters.Whitelist + for _, vLog := range logs { + if !indexer.isRelevantLog(vLog.Topics[0]) { + continue + } + transferData, err := indexer.ProcessLog(ctx, chainID, client, vLog) + if err != nil { + return nil, err + } + result = append(result, *transferData) + } + return result, nil +} + +func (indexer *ContractDeployedIndexer) isRelevantLog(topic common.Hash) bool { + return topic.Hex() == logContractDeployedSigHash.Hex() +} + +func (indexer *ContractDeployedIndexer) ProcessLog(ctx context.Context, chainID *big.Int, client *ethclient.Client, vLog types.Log) (*adapters.Whitelist, error) { + txn, err := client.TransactionInBlock(ctx, vLog.BlockHash, vLog.TxIndex) + if err != nil { + return nil, err + } + + sender, err := client.TransactionSender(ctx, txn, vLog.BlockHash, vLog.TxIndex) + if err != nil { + return nil, err + } + + block, err := client.BlockByNumber(ctx, big.NewInt(int64(vLog.BlockNumber))) + if err != nil { + return nil, err + } + + return &adapters.Whitelist{ + User: sender, + Time: block.Time(), + BlockNumber: block.Number().Uint64(), + }, nil +} diff --git a/cmd/README.md b/cmd/README.md new file mode 100644 index 0000000..4642565 --- /dev/null +++ b/cmd/README.md @@ -0,0 +1,17 @@ +# Trailblazer Adapters CLI + +## Adding a New CLI Command + +### 1. Add a new option under ```adapterOptions``` in ```root.go``` + +### 2. Import adapter + +An example on how to add import a newly added adapter [here](./order_fulfilled.go) + +### 3. Test out the CLI + +Test out the CLI with the following command and input your parameters accordingly. +``` +go run main.go process +``` + diff --git a/cmd/collection_created.go b/cmd/collection_created.go new file mode 100644 index 0000000..a85d7a5 --- /dev/null +++ b/cmd/collection_created.go @@ -0,0 +1,11 @@ +package cmd + +import ( + "github.com/ethereum/go-ethereum/ethclient" + "github.com/taikoxyz/trailblazer-adapters/adapters/projects/nfts2me" +) + +func processCollectionCreatedIndexer(client *ethclient.Client, blockNumber int64) error { + processor := nfts2me.NewCollectionCreatedIndexer() + return processLogIndexer(client, processor, blockNumber) +} diff --git a/cmd/contract_deployed.go b/cmd/contract_deployed.go new file mode 100644 index 0000000..15cc25a --- /dev/null +++ b/cmd/contract_deployed.go @@ -0,0 +1,11 @@ +package cmd + +import ( + "github.com/ethereum/go-ethereum/ethclient" + "github.com/taikoxyz/trailblazer-adapters/adapters/projects/omnihub" +) + +func processContractDeployedIndexer(client *ethclient.Client, blockNumber int64) error { + processor := omnihub.NewContractDeployedIndexer() + return processLogIndexer(client, processor, blockNumber) +} diff --git a/cmd/domain_register.go b/cmd/domain_register.go index cd963fc..8b6e9c3 100644 --- a/cmd/domain_register.go +++ b/cmd/domain_register.go @@ -2,10 +2,10 @@ package cmd import ( "github.com/ethereum/go-ethereum/ethclient" - "github.com/taikoxyz/trailblazer-adapters/adapters/logs" + "github.com/taikoxyz/trailblazer-adapters/adapters/projects/domains" ) -func processDotTaikoIndexer(client *ethclient.Client, blockNumber int64) { - processor := logs.NewDotTaikoIndexer() - processLogIndexer(client, processor, blockNumber) +func processDotTaikoIndexer(client *ethclient.Client, blockNumber int64) error { + processor := domains.NewDotTaikoIndexer() + return processLogIndexer(client, processor, blockNumber) } diff --git a/cmd/new_sale.go b/cmd/new_sale.go index 99e419c..08c8fb8 100644 --- a/cmd/new_sale.go +++ b/cmd/new_sale.go @@ -2,10 +2,10 @@ package cmd import ( "github.com/ethereum/go-ethereum/ethclient" - "github.com/taikoxyz/trailblazer-adapters/adapters/logs" + "github.com/taikoxyz/trailblazer-adapters/adapters/projects/loopex" ) -func processNewSaleIndexer(client *ethclient.Client, blockNumber int64) { - processor := logs.NewNewSaleIndexer() - processLogIndexer(client, processor, blockNumber) +func processNewSaleIndexer(client *ethclient.Client, blockNumber int64) error { + processor := loopex.NewNewSaleIndexer() + return processLogIndexer(client, processor, blockNumber) } diff --git a/cmd/order_fulfilled.go b/cmd/order_fulfilled.go index 1625540..0a6ada6 100644 --- a/cmd/order_fulfilled.go +++ b/cmd/order_fulfilled.go @@ -2,10 +2,10 @@ package cmd import ( "github.com/ethereum/go-ethereum/ethclient" - "github.com/taikoxyz/trailblazer-adapters/adapters/logs" + "github.com/taikoxyz/trailblazer-adapters/adapters/projects/okx" ) -func processOrderFulfilledIndexer(client *ethclient.Client, blockNumber int64) { - processor := logs.NewOrderFulfilledIndexer() - processLogIndexer(client, processor, blockNumber) +func processOrderFulfilledIndexer(client *ethclient.Client, blockNumber int64) error { + processor := okx.NewOrderFulfilledIndexer() + return processLogIndexer(client, processor, blockNumber) } diff --git a/cmd/process_log.go b/cmd/process_log.go index 37d322d..43878d9 100644 --- a/cmd/process_log.go +++ b/cmd/process_log.go @@ -11,10 +11,11 @@ import ( "github.com/taikoxyz/trailblazer-adapters/adapters" ) -func processLogIndexer(client *ethclient.Client, processor adapters.TransferLogsIndexer, blockNumber int64) { +func processLogIndexer(client *ethclient.Client, processor adapters.TransferLogsIndexer, blockNumber int64) error { chainID, err := client.ChainID(context.Background()) if err != nil { log.Fatalf("Failed to fetch the chain ID: %v", err) + return err } query := ethereum.FilterQuery{ Addresses: processor.Addresses(), @@ -24,11 +25,14 @@ func processLogIndexer(client *ethclient.Client, processor adapters.TransferLogs logs, err := client.FilterLogs(context.Background(), query) if err != nil { log.Fatalf("Failed to fetch the logs: %v", err) + return err } senders, err := processor.IndexLogs(context.Background(), chainID, client, logs) if err != nil { log.Fatalf("Failed to process the logs: %v", err) + return err } fmt.Printf("Senders: %v\n", senders) + return nil } diff --git a/cmd/process_transaction.go b/cmd/process_transaction.go new file mode 100644 index 0000000..81004c6 --- /dev/null +++ b/cmd/process_transaction.go @@ -0,0 +1,29 @@ +package cmd + +import ( + "context" + "fmt" + "log" + "math/big" + + "github.com/ethereum/go-ethereum/ethclient" + "github.com/taikoxyz/trailblazer-adapters/adapters" +) + +func processTransactionIndexer(client *ethclient.Client, processor adapters.BlockProcessor, blockNumber int64) error { + blockNumberBig := big.NewInt(blockNumber) + block, err := client.BlockByNumber(context.Background(), blockNumberBig) + if err != nil { + log.Fatalf("Failed to fetch the block: %v", err) + return err + } + + senders, err := processor.ProcessBlock(context.Background(), block, client) + if err != nil { + log.Fatalf("Failed to process the block: %v", err) + return err + } + + fmt.Printf("Senders: %v\n", senders) + return nil +} diff --git a/cmd/root.go b/cmd/root.go index 7f4da7b..b4d0d2e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -12,14 +12,6 @@ import ( var adapter string var blockNumber int64 -var rootCmd = &cobra.Command{ - Use: "trailblazer-adapters", - Short: "Trailblazer Adapters CLI", - Run: func(cmd *cobra.Command, args []string) { - promptUser() - }, -} - func Execute() { if err := rootCmd.Execute(); err != nil { fmt.Println(err) @@ -27,12 +19,30 @@ func Execute() { } } +var rootCmd = &cobra.Command{ + Use: "trailblazer-adapters", + Short: "Trailblazer Adapters CLI", + Run: func(cmd *cobra.Command, args []string) { + if err := promptUser(); err != nil { + log.Fatalf("Prompt failed: %v", err) + } + if err := executeCommand(); err != nil { + log.Fatalf("Execution failed: %v", err) + } + }, +} + func init() { cobra.OnInitialize() } -func promptUser() { - var adapterOptions = []string{"NewTransactionSender", "DotTaikoIndexer", "OrderFulfilledIndexer", "NewSaleIndexer"} +func promptUser() error { + var adapterOptions = []string{ + "NewTransactionSender", "NftDeployed", "DotTaikoIndexer", + "OrderFulfilledIndexer", "NewSaleIndexer", "ContractDeployed", + "CollectionCreated", "TokenSold", + } + var qs = []*survey.Question{ { Name: "adapter", @@ -52,33 +62,39 @@ func promptUser() { BlockNumber int64 `survey:"blockNumber"` }{} - err := survey.Ask(qs, &answers) - if err != nil { - log.Fatalf("Prompt failed %v", err) + if err := survey.Ask(qs, &answers); err != nil { + return err } adapter = answers.Adapter blockNumber = answers.BlockNumber - - executeCommand() + return nil } -func executeCommand() { +func executeCommand() error { client, err := ethclient.Dial("https://rpc.taiko.xyz") if err != nil { - log.Fatalf("Failed to connect to the Ethereum client: %v", err) + return fmt.Errorf("failed to connect to the Ethereum client: %v", err) } switch adapter { case "NewTransactionSender": - processNewTransactionSender(client, blockNumber) + return processNewTransactionSender(client, blockNumber) + case "NftDeployed": + return processNewNftDeployed(client, blockNumber) case "OrderFulfilledIndexer": - processOrderFulfilledIndexer(client, blockNumber) + return processOrderFulfilledIndexer(client, blockNumber) case "DotTaikoIndexer": - processDotTaikoIndexer(client, blockNumber) + return processDotTaikoIndexer(client, blockNumber) case "NewSaleIndexer": - processNewSaleIndexer(client, blockNumber) + return processNewSaleIndexer(client, blockNumber) + case "ContractDeployed": + return processContractDeployedIndexer(client, blockNumber) + case "CollectionCreated": + return processCollectionCreatedIndexer(client, blockNumber) + case "TokenSold": + return processTokenSoldIndexer(client, blockNumber) default: - log.Fatalf("Adapter %s is not supported", adapter) + return fmt.Errorf("adapter %s is not supported", adapter) } } diff --git a/cmd/token_sold.go b/cmd/token_sold.go new file mode 100644 index 0000000..7d2f0f7 --- /dev/null +++ b/cmd/token_sold.go @@ -0,0 +1,11 @@ +package cmd + +import ( + "github.com/ethereum/go-ethereum/ethclient" + "github.com/taikoxyz/trailblazer-adapters/adapters/projects/conft" +) + +func processTokenSoldIndexer(client *ethclient.Client, blockNumber int64) error { + processor := conft.NewTokenSoldIndexer() + return processLogIndexer(client, processor, blockNumber) +} diff --git a/cmd/transaction_sender.go b/cmd/transaction_sender.go index 397b20f..e90b5ba 100644 --- a/cmd/transaction_sender.go +++ b/cmd/transaction_sender.go @@ -1,27 +1,16 @@ package cmd import ( - "context" - "fmt" - "log" - "math/big" - "github.com/ethereum/go-ethereum/ethclient" "github.com/taikoxyz/trailblazer-adapters/adapters/blocks" ) -func processNewTransactionSender(client *ethclient.Client, blockNumber int64) { +func processNewTransactionSender(client *ethclient.Client, blockNumber int64) error { processor := blocks.NewTransactionSender() - blockNumberBig := big.NewInt(blockNumber) - block, err := client.BlockByNumber(context.Background(), blockNumberBig) - if err != nil { - log.Fatalf("Failed to fetch the block: %v", err) - } - - senders, err := processor.ProcessBlock(context.Background(), block, client) - if err != nil { - log.Fatalf("Failed to process the block: %v", err) - } + return processTransactionIndexer(client, processor, blockNumber) +} - fmt.Printf("Senders: %v\n", senders) +func processNewNftDeployed(client *ethclient.Client, blockNumber int64) error { + processor := blocks.NewNftDeployedIndexer() + return processTransactionIndexer(client, processor, blockNumber) }