[docs]classBlockchainType(Enum):"""Enumerator of the supported blockchains."""ETH_MAINNET="eth-mainnet"ETH_GOERLI="eth-goerli"ETH_SEPOLIA="eth-sepolia"ETH_HOLESKY="eth-holesky"POLYGON_MAINNET="polygon-mainnet"POLYGON_MUMBAI="polygon-mumbai"POLYGON_AMOY="polygon-amoy"ARB_MAINNET="arb-mainnet"ARB_SEPOLIA="arb-sepolia"OP_MAINNET="opt-mainnet"OP_SEPOLIA="opt-sepolia"BASE_MAINNET="base-mainnet"BASE_SEPOLIA="base-sepolia"BLAST_MAINNET="blast-mainnet"BLAST_SEPOLIA="blast-sepolia"ZKSYNC_MAINNET="zksync-mainnet"ZKSYNC_SEPOLIA="zksync-sepolia"ZORA_MAINNET="zora-mainnet"ZORA_SEPOLIA="zora-sepolia"
[docs]classBlockchainDocumentLoader(BaseLoader):"""Load elements from a blockchain smart contract. See supported blockchains here: https://python.langchain.com/v0.2/api_reference/community/document_loaders/langchain_community.document_loaders.blockchain.BlockchainType.html If no BlockchainType is specified, the default is Ethereum mainnet. The Loader uses the Alchemy API to interact with the blockchain. ALCHEMY_API_KEY environment variable must be set to use this loader. The API returns 100 NFTs per request and can be paginated using the startToken parameter. If get_all_tokens is set to True, the loader will get all tokens on the contract. Note that for contracts with a large number of tokens, this may take a long time (e.g. 10k tokens is 100 requests). Default value is false for this reason. The max_execution_time (sec) can be set to limit the execution time of the loader. Future versions of this loader can: - Support additional Alchemy APIs (e.g. getTransactions, etc.) - Support additional blockchain APIs (e.g. Infura, Opensea, etc.) """# noqa: E501
[docs]def__init__(self,contract_address:str,blockchainType:BlockchainType=BlockchainType.ETH_MAINNET,api_key:str="docs-demo",startToken:str="",get_all_tokens:bool=False,max_execution_time:Optional[int]=None,):""" Args: contract_address: The address of the smart contract. blockchainType: The blockchain type. api_key: The Alchemy API key. startToken: The start token for pagination. get_all_tokens: Whether to get all tokens on the contract. max_execution_time: The maximum execution time (sec). """self.contract_address=contract_addressself.blockchainType=blockchainType.valueself.api_key=os.environ.get("ALCHEMY_API_KEY")orapi_keyself.startToken=startTokenself.get_all_tokens=get_all_tokensself.max_execution_time=max_execution_timeifnotself.api_key:raiseValueError("Alchemy API key not provided.")ifnotre.match(r"^0x[a-fA-F0-9]{40}$",self.contract_address):raiseValueError(f"Invalid contract address {self.contract_address}")
[docs]defload(self)->List[Document]:result=[]current_start_token=self.startTokenstart_time=time.time()whileTrue:url=(f"https://{self.blockchainType}.g.alchemy.com/nft/v2/"f"{self.api_key}/getNFTsForCollection?withMetadata="f"True&contractAddress={self.contract_address}"f"&startToken={current_start_token}")response=requests.get(url)ifresponse.status_code!=200:raiseValueError(f"Request failed with status code {response.status_code}")items=response.json()["nfts"]ifnotitems:breakforiteminitems:content=str(item)tokenId=item["id"]["tokenId"]metadata={"source":self.contract_address,"blockchain":self.blockchainType,"tokenId":tokenId,}result.append(Document(page_content=content,metadata=metadata))# exit after the first API call if get_all_tokens is Falseifnotself.get_all_tokens:break# get the start token for the next API call from the last item in arraycurrent_start_token=self._get_next_tokenId(result[-1].metadata["tokenId"])if(self.max_execution_timeisnotNoneand(time.time()-start_time)>self.max_execution_time):raiseRuntimeError("Execution time exceeded the allowed time limit.")ifnotresult:raiseValueError(f"No NFTs found for contract address {self.contract_address}")returnresult
# add one to the tokenId, ensuring the correct tokenId format is useddef_get_next_tokenId(self,tokenId:str)->str:value_type=self._detect_value_type(tokenId)ifvalue_type=="hex_0x":value_int=int(tokenId,16)elifvalue_type=="hex_0xbf":value_int=int(tokenId[2:],16)else:value_int=int(tokenId)result=value_int+1ifvalue_type=="hex_0x":return"0x"+format(result,"0"+str(len(tokenId)-2)+"x")elifvalue_type=="hex_0xbf":return"0xbf"+format(result,"0"+str(len(tokenId)-4)+"x")else:returnstr(result)# A smart contract can use different formats for the tokenId@staticmethoddef_detect_value_type(tokenId:str)->str:ifisinstance(tokenId,int):return"int"eliftokenId.startswith("0x"):return"hex_0x"eliftokenId.startswith("0xbf"):return"hex_0xbf"else:return"hex_0xbf"