Skip to content

Commit

Permalink
Merge pull request #8 from taikoxyz/nft-trail
Browse files Browse the repository at this point in the history
feat: add nft trails whitelist
  • Loading branch information
bennettyong authored Jul 26, 2024
2 parents 5c99052 + 47170bc commit 1c55653
Show file tree
Hide file tree
Showing 25 changed files with 615 additions and 93 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.
8 changes: 7 additions & 1 deletion adapters/blocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
112 changes: 112 additions & 0 deletions adapters/blocks/nft_deployed.go
Original file line number Diff line number Diff line change
@@ -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
}
14 changes: 8 additions & 6 deletions adapters/blocks/transaction_sender.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import (
"github.com/taikoxyz/trailblazer-adapters/adapters"
)

var _ adapters.BlockProcessor = (*TransactionSender)(nil)

type TransactionSender struct {
ValidRecipient map[string]struct{}
}
Expand All @@ -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())
Expand All @@ -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
}
49 changes: 49 additions & 0 deletions adapters/contracts/conft/abi.go
Original file line number Diff line number Diff line change
@@ -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"
}
]`
)
25 changes: 25 additions & 0 deletions adapters/contracts/erc165/abi.go
Original file line number Diff line number Diff line change
@@ -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"
}
]`
)
13 changes: 2 additions & 11 deletions adapters/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
10 changes: 5 additions & 5 deletions adapters/logs/transfer_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:])

Expand All @@ -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
Expand Down
85 changes: 85 additions & 0 deletions adapters/projects/conft/token_sold.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit 1c55653

Please sign in to comment.