-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(eventindexer): indexing nft metadata (#17538)
Co-authored-by: Jeffery Walsh <cyberhorsey@gmail.com> Co-authored-by: jeff <113397187+cyberhorsey@users.noreply.github.com> Co-authored-by: cyberhorsey <cyberhorsey@users.noreply.github.com>
- Loading branch information
1 parent
d2b00ce
commit d0e25ba
Showing
27 changed files
with
991 additions
and
100 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package erc721 | ||
|
||
var ( | ||
ABI = `[ | ||
{ | ||
"constant":true, | ||
"inputs":[ | ||
{ | ||
"name":"_tokenId", | ||
"type":"uint256" | ||
} | ||
], | ||
"name":"tokenURI", | ||
"outputs":[ | ||
{ | ||
"name":"", | ||
"type":"string" | ||
} | ||
], | ||
"payable":false, | ||
"stateMutability":"view", | ||
"type":"function" | ||
}, | ||
{ | ||
"constant": true, | ||
"inputs": [], | ||
"name": "symbol", | ||
"outputs": [ | ||
{ | ||
"name": "", | ||
"type": "string" | ||
} | ||
], | ||
"payable": false, | ||
"stateMutability": "view", | ||
"type": "function" | ||
} | ||
]` | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
package indexer | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"log/slog" | ||
"math/big" | ||
"net/http" | ||
"strings" | ||
"time" | ||
|
||
"github.com/ethereum/go-ethereum" | ||
"github.com/ethereum/go-ethereum/accounts/abi" | ||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/pkg/errors" | ||
"github.com/taikoxyz/taiko-mono/packages/eventindexer" | ||
"github.com/taikoxyz/taiko-mono/packages/eventindexer/contracts/erc1155" | ||
"github.com/taikoxyz/taiko-mono/packages/eventindexer/contracts/erc721" | ||
) | ||
|
||
func (i *Indexer) fetchNFTMetadata( | ||
ctx context.Context, contractAddress string, | ||
tokenID *big.Int, | ||
abiJSON string, | ||
methodName string, | ||
chainID *big.Int) (*eventindexer.NFTMetadata, error) { | ||
contractABI, err := abi.JSON(strings.NewReader(abiJSON)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
contractAddressCommon := common.HexToAddress(contractAddress) | ||
|
||
callData, err := contractABI.Pack(methodName, tokenID) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
msg := ethereum.CallMsg{ | ||
To: &contractAddressCommon, | ||
Data: callData, | ||
} | ||
|
||
result, err := i.ethClient.CallContract(ctx, msg, nil) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "i.ethClient.CallContract") | ||
} | ||
|
||
var tokenURI string | ||
|
||
err = contractABI.UnpackIntoInterface(&tokenURI, methodName, result) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "contractABI.UnpackIntoInterface") | ||
} | ||
|
||
url, err := resolveMetadataURL(ctx, tokenURI) | ||
if err != nil { | ||
if errors.Is(err, eventindexer.ErrInvalidURL) { | ||
slog.Warn("Invalid metadata URI", | ||
"contractAddress", contractAddress, | ||
"tokenID", tokenID.Int64(), | ||
"chainID", chainID.String()) | ||
|
||
return nil, nil | ||
} | ||
|
||
return nil, errors.Wrap(err, "resolveMetadataURL") | ||
} | ||
|
||
//nolint | ||
resp, err := http.Get(url) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
defer resp.Body.Close() | ||
|
||
var metadata eventindexer.NFTMetadata | ||
|
||
err = json.NewDecoder(resp.Body).Decode(&metadata) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if methodName == "tokenURI" { | ||
if err := i.fetchSymbol(ctx, contractABI, &metadata, contractAddressCommon); err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
metadata.ContractAddress = contractAddress | ||
metadata.TokenID = tokenID.Int64() | ||
metadata.ChainID = chainID.Int64() | ||
|
||
return &metadata, nil | ||
} | ||
|
||
func resolveMetadataURL(ctx context.Context, tokenURI string) (string, error) { | ||
if strings.HasPrefix(tokenURI, "ipfs://") { | ||
ipfsHash := strings.TrimPrefix(tokenURI, "ipfs://") | ||
resolvedURL := fmt.Sprintf("https://ipfs.io/ipfs/%s", ipfsHash) | ||
|
||
if isValidURL(ctx, resolvedURL) { | ||
return resolvedURL, nil | ||
} | ||
|
||
return "", eventindexer.ErrInvalidURL | ||
} | ||
|
||
if isValidURL(ctx, tokenURI) { | ||
return tokenURI, nil | ||
} | ||
|
||
return "", eventindexer.ErrInvalidURL | ||
} | ||
|
||
func isValidURL(ctx context.Context, rawURL string) bool { | ||
client := &http.Client{ | ||
Timeout: 3 * time.Second, | ||
} | ||
|
||
resp, err := client.Head(rawURL) | ||
if err != nil || resp.StatusCode != http.StatusOK { | ||
return false | ||
} | ||
|
||
return true | ||
} | ||
|
||
func (i *Indexer) fetchSymbol(ctx context.Context, contractABI abi.ABI, metadata *eventindexer.NFTMetadata, contractAddress common.Address) error { | ||
symbolCallData, err := contractABI.Pack("symbol") | ||
if err != nil { | ||
return errors.Wrap(err, "contractABI.Pack") | ||
} | ||
|
||
symbolMsg := ethereum.CallMsg{ | ||
To: &contractAddress, | ||
Data: symbolCallData, | ||
} | ||
|
||
symbolResult, err := i.ethClient.CallContract(ctx, symbolMsg, nil) | ||
if err != nil { | ||
return errors.Wrap(err, "i.ethClient.CallContract(symbolMsg)") | ||
} | ||
|
||
var symbol string | ||
|
||
err = contractABI.UnpackIntoInterface(&symbol, "symbol", symbolResult) | ||
if err != nil { | ||
return errors.Wrap(err, "contractABI.UnpackIntoInterface") | ||
} | ||
|
||
metadata.Symbol = symbol | ||
|
||
return nil | ||
} | ||
|
||
func (i *Indexer) fetchERC721Metadata(ctx context.Context, | ||
contractAddress string, | ||
tokenID *big.Int, | ||
chainID *big.Int) (*eventindexer.NFTMetadata, error) { | ||
return i.fetchNFTMetadata(ctx, contractAddress, tokenID, erc721.ABI, "tokenURI", chainID) | ||
} | ||
|
||
func (i *Indexer) fetchERC1155Metadata(ctx context.Context, | ||
contractAddress string, | ||
tokenID *big.Int, | ||
chainID *big.Int) (*eventindexer.NFTMetadata, error) { | ||
return i.fetchNFTMetadata(ctx, contractAddress, tokenID, erc1155.ABI, "uri", chainID) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.