oli-python
Advanced tools
+438
| import requests | ||
| import yaml | ||
| class API: | ||
| def __init__(self, oli_client, api_key: str=None): | ||
| """ | ||
| Initialize the DataFetcher with an OLI client. | ||
| Args: | ||
| oli_client: The OLI client instance | ||
| """ | ||
| self.oli = oli_client | ||
| self.api_url = "https://api.openlabelsinitiative.org" | ||
| self.api_key = api_key | ||
| ### OLI Tags and Value Sets maintained in Github ### | ||
| def get_OLI_tags(self) -> dict: | ||
| """ | ||
| Get latest OLI tags from OLI Github repo. | ||
| Returns: | ||
| dict: Dictionary of official OLI tags | ||
| """ | ||
| url = "https://raw.githubusercontent.com/openlabelsinitiative/OLI/refs/heads/main/1_label_schema/tags/tag_definitions.yml" | ||
| try: | ||
| response = requests.get(url) | ||
| if response.status_code == 200: | ||
| y = yaml.safe_load(response.text) | ||
| y = {i['tag_id']: i for i in y['tags']} | ||
| return y | ||
| else: | ||
| print(f"Failed to fetch OLI tags from Github, are you offline? (or are you not using the latest version of the oli-python package? 'pip install --upgrade oli-python'): {response.status_code}") | ||
| except Exception as e: | ||
| print(f"Failed to fetch OLI tags from Github (are you using the latest version of the oli-python package? 'pip install --upgrade oli-python'): {e}") | ||
| def get_OLI_value_sets(self) -> dict: | ||
| """ | ||
| Get latest value sets for OLI tags. | ||
| Returns: | ||
| dict: Dictionary of value sets with tag_id as key | ||
| """ | ||
| value_sets = {} | ||
| # Extract value sets from tag definitions (must be a list) | ||
| for tag_def in self.oli.tag_definitions.values(): | ||
| if 'schema' not in tag_def: | ||
| continue | ||
| schema = tag_def['schema'] | ||
| tag_id = tag_def['tag_id'] | ||
| value_set = None | ||
| # Get enum from direct schema or array items | ||
| if 'enum' in schema: | ||
| value_set = schema['enum'] | ||
| elif (schema.get('type') == 'array' and | ||
| 'items' in schema and | ||
| 'enum' in schema['items']): | ||
| value_set = schema['items']['enum'] | ||
| # Process and add to value_sets | ||
| if value_set and isinstance(value_set, list): | ||
| value_sets[tag_id] = [i.lower() if isinstance(i, str) else i for i in value_set] | ||
| # value set for owner_project | ||
| url = "https://api.growthepie.com/v1/labels/projects.json" | ||
| try: | ||
| response = requests.get(url) | ||
| if response.status_code == 200: | ||
| y = yaml.safe_load(response.text) | ||
| value_sets["owner_project"] = [i[0] for i in y['data']['data']] | ||
| value_sets["owner_project"] = [i.lower() if isinstance(i, str) else i for i in value_sets["owner_project"]] | ||
| else: | ||
| print(f"Failed to fetch owner_project value set from growthepie projects api, are you offline? (or are you not using the latest version of the oli-python package? 'pip install --upgrade oli-python'): {response.status_code}") | ||
| except Exception as e: | ||
| print(f"Failed to fetch owner_project value set from growthepie projects api (are you using the latest version of the oli-python package? 'pip install --upgrade oli-python'): {e}") | ||
| # value set for usage_category | ||
| url = "https://raw.githubusercontent.com/openlabelsinitiative/OLI/refs/heads/main/1_label_schema/tags/valuesets/usage_category.yml" | ||
| try: | ||
| response = requests.get(url) | ||
| if response.status_code == 200: | ||
| y = yaml.safe_load(response.text) | ||
| value_sets['usage_category'] = [i['category_id'] for i in y['categories']] | ||
| value_sets['usage_category'] = [i.lower() if isinstance(i, str) else i for i in value_sets['usage_category']] | ||
| else: | ||
| print(f"Failed to fetch usage_category value set from OLI Github, are you offline? (or are you not using the latest version of the oli-python package? 'pip install --upgrade oli-python'): {response.status_code}") | ||
| except Exception as e: | ||
| print(f"Failed to fetch usage_category value set from OLI Github (are you using the latest version of the oli-python package? 'pip install --upgrade oli-python'): {e}") | ||
| return value_sets | ||
| ### Parquet exports of OLI Label Pool by growthepie ### | ||
| def get_full_raw_export_parquet(self, file_path: str="raw_labels.parquet") -> str: | ||
| """ | ||
| Downloads the full raw export of all labels in the OLI Label Pool as a Parquet file. | ||
| Args: | ||
| file_path (str): Path where the file will be saved. Defaults to "raw_labels.parquet". | ||
| Returns: | ||
| str: Path to the downloaded Parquet file | ||
| """ | ||
| url = "https://api.growthepie.com/v1/oli/labels_raw.parquet" | ||
| response = requests.get(url, stream=True) | ||
| if response.status_code == 200: | ||
| with open(file_path, 'wb') as f: | ||
| f.write(response.content) | ||
| print(f"Downloaded and saved: {file_path}") | ||
| return file_path | ||
| else: | ||
| print(f"Failed to download {url}. Status code: {response.status_code}") | ||
| return None | ||
| def get_full_decoded_export_parquet(self, file_path: str="decoded_labels.parquet") -> str: | ||
| """ | ||
| Downloads the full decoded export of all labels in the OLI Label Pool as a Parquet file. | ||
| Args: | ||
| file_path (str): Path where the file will be saved. Defaults to "decoded_labels.parquet". | ||
| Returns: | ||
| str: Path to the downloaded Parquet file | ||
| """ | ||
| url = "https://api.growthepie.com/v1/oli/labels_decoded.parquet" | ||
| response = requests.get(url, stream=True) | ||
| if response.status_code == 200: | ||
| with open(file_path, 'wb') as f: | ||
| f.write(response.content) | ||
| print(f"Downloaded and saved: {file_path}") | ||
| return file_path | ||
| else: | ||
| print(f"Failed to download {url}. Status code: {response.status_code}") | ||
| return None | ||
| ### OLI API ENDPOINTS ### | ||
| def post_single_attestation(self, attestation: dict) -> dict: | ||
| """ | ||
| Post a single attestation to the OLI Label Pool. | ||
| Args: | ||
| attestation (dict): The attestation object containing sig and signer | ||
| Returns: | ||
| dict: Response with uid and status (e.g., {"uid": "string", "status": "queued"}) | ||
| """ | ||
| url = f"{self.api_url}/attestation" | ||
| response = requests.post(url, json=attestation) | ||
| if response.status_code == 200: | ||
| return response | ||
| else: | ||
| raise Exception(f"Failed to post single attestation: {response.status_code} - {response.text}") | ||
| def post_bulk_attestations(self, attestations: list) -> dict: | ||
| """ | ||
| Post multiple attestations to the OLI Label Pool in bulk. | ||
| Args: | ||
| attestations (list): List of attestation objects (1-1000 items) | ||
| Returns: | ||
| dict: Response with accepted count, duplicates, failed_validation, and status | ||
| """ | ||
| url = f"{self.api_url}/attestations/bulk" | ||
| if not 1 <= len(attestations) <= 1000: | ||
| raise ValueError("attestations must contain between 1 and 1000 items") | ||
| payload = {"attestations": attestations} | ||
| response = requests.post(url, json=payload) | ||
| if response.status_code == 200: | ||
| return response | ||
| else: | ||
| raise Exception(f"Failed to post bulk attestations: {response.status_code} - {response.text}") | ||
| def get_attestations(self, uid: str = None, attester: str = None, recipient: str = None, schema_info: str = None, since: str = None, order: str = "desc", limit: int = 1000) -> dict: | ||
| """ | ||
| Get raw attestations from storage with optional filters. | ||
| Args: | ||
| uid (str): Filter by specific attestation UID (0x...). If provided, other filters are ignored | ||
| attester (str): Filter by attester address (0x...) | ||
| recipient (str): Filter by recipient address (0x...) | ||
| schema_info (str): Filter by schema_info (e.g. '8453__0xabc...') | ||
| since (str): Return only attestations created after this timestamp (ISO8601 or Unix seconds) | ||
| order (str): Order results by attestation time ('asc' or 'desc'). Default: 'desc' | ||
| limit (int): Max number of attestations to return (1-1000). Default: 1000 | ||
| Returns: | ||
| dict: Response with count and attestations array | ||
| """ | ||
| url = f"{self.api_url}/attestations" | ||
| params = {} | ||
| if uid: | ||
| params['uid'] = uid | ||
| if attester: | ||
| params['attester'] = attester | ||
| if recipient: | ||
| params['recipient'] = recipient | ||
| if schema_info: | ||
| params['schema_info'] = schema_info | ||
| if since: | ||
| params['since'] = since | ||
| if order: | ||
| params['order'] = order | ||
| if limit: | ||
| params['limit'] = limit | ||
| response = requests.get(url, params=params) | ||
| if response.status_code == 200: | ||
| return response.json() | ||
| else: | ||
| raise Exception(f"Failed to get attestations: {response.status_code} - {response.text}") | ||
| def post_trust_list(self, trust_list: dict) -> dict: | ||
| """ | ||
| Post a trust list to the OLI Label Pool. | ||
| Args: | ||
| trust_list (dict): The trust list object | ||
| Returns: | ||
| dict: Response with uid and status (e.g., {"uid": "string", "status": "queued"}) | ||
| """ | ||
| url = f"{self.api_url}/trust-list" | ||
| response = requests.post(url, json=trust_list) | ||
| if response.status_code == 200: | ||
| return response | ||
| else: | ||
| raise Exception(f"Failed to post trust list: {response.status_code} - {response.text}") | ||
| def get_trust_lists(self, uid: str = None, attester: str = None, order: str = "desc", limit: int = 1000) -> dict: | ||
| """ | ||
| Get trust lists with optional filters. | ||
| Args: | ||
| uid (str): Filter by specific trust list UID (0x...) | ||
| attester (str): Filter by attester address (0x...) | ||
| order (str): Order by time ('asc' or 'desc'). Default: 'desc' | ||
| limit (int): Max number of rows to return (1-1000). Default: 1000 | ||
| Returns: | ||
| dict: Response with count and trust_lists array | ||
| """ | ||
| url = f"{self.api_url}/trust-lists" | ||
| params = {} | ||
| if uid: | ||
| params['uid'] = uid | ||
| if attester: | ||
| params['attester'] = attester | ||
| if order: | ||
| params['order'] = order | ||
| if limit: | ||
| params['limit'] = limit | ||
| response = requests.get(url, params=params) | ||
| if response.status_code == 200: | ||
| return response.json() | ||
| else: | ||
| raise Exception(f"Failed to get trust lists: {response.status_code} - {response.text}") | ||
| def get_labels(self, address: str, chain_id: str = None, limit: int = 100, include_all: bool = False) -> dict: | ||
| """ | ||
| Get labels (key/value) for a given address. Requires API key authentication. | ||
| By default, returns only the newest label per (chain_id, attester, tag_id). | ||
| Args: | ||
| address (str): Address (0x...) [required] | ||
| chain_id (str): Optional chain_id filter (e.g., 'eip155:8453') | ||
| limit (int): Max number of labels to return (<=1000). Default: 100 | ||
| include_all (bool): If False (default), return only the latest label per (chain_id, attester, tag_id) | ||
| Returns: | ||
| dict: Response with address, count, and labels array | ||
| Raises: | ||
| ValueError: If api_key is not provided | ||
| """ | ||
| if not self.api_key: | ||
| raise ValueError( | ||
| "API key is required for this endpoint. This is a protected endpoint that requires authentication.\n" | ||
| "Protected endpoints: get_labels, get_labels_bulk, search_addresses_by_tag, get_attester_analytics\n" | ||
| "Please provide an api_key parameter when calling this method.\n" | ||
| "You can obtain a free API key at https://openlabelsinitiative.org/signup" | ||
| ) | ||
| url = f"{self.api_url}/labels" | ||
| headers = {'X-API-Key': self.api_key} | ||
| params = {'address': address} | ||
| if chain_id: | ||
| params['chain_id'] = chain_id | ||
| if limit: | ||
| params['limit'] = limit | ||
| if include_all is not None: | ||
| params['include_all'] = include_all | ||
| response = requests.get(url, params=params, headers=headers) | ||
| if response.status_code == 200: | ||
| return response.json() | ||
| else: | ||
| raise Exception(f"Failed to get labels: {response.status_code} - {response.text}") | ||
| def get_labels_bulk(self, addresses: list, chain_id: str = None, limit_per_address: int = 1000, include_all: bool = False) -> dict: | ||
| """ | ||
| Get labels for multiple addresses in bulk. Requires API key authentication. | ||
| Args: | ||
| addresses (list): List of addresses (1-100 items) [required] | ||
| chain_id (str): Optional chain_id filter (e.g., 'eip155:8453') | ||
| limit_per_address (int): Max labels to return per address (1-1000). Default: 1000 | ||
| include_all (bool): Include all labels vs only latest per (chain_id, attester, tag_id). Default: False | ||
| Returns: | ||
| dict: Response with results array containing address and labels for each address | ||
| Raises: | ||
| ValueError: If api_key is not provided or if addresses list is invalid | ||
| """ | ||
| if not self.api_key: | ||
| raise ValueError( | ||
| "API key is required for this endpoint. This is a protected endpoint that requires authentication.\n" | ||
| "Protected endpoints: get_labels, get_labels_bulk, search_addresses_by_tag, get_attester_analytics\n" | ||
| "Please provide an api_key parameter when calling this method.\n" | ||
| "You can obtain a free API key at https://openlabelsinitiative.org/signup" | ||
| ) | ||
| url = f"{self.api_url}/labels/bulk" | ||
| if not 1 <= len(addresses) <= 100: | ||
| raise ValueError("addresses must contain between 1 and 100 items") | ||
| headers = {'X-API-Key': self.api_key} | ||
| payload = { | ||
| "addresses": addresses, | ||
| "limit_per_address": limit_per_address, | ||
| "include_all": include_all | ||
| } | ||
| if chain_id: | ||
| payload['chain_id'] = chain_id | ||
| response = requests.post(url, json=payload, headers=headers) | ||
| if response.status_code == 200: | ||
| return response.json() | ||
| else: | ||
| raise Exception(f"Failed to get labels bulk: {response.status_code} - {response.text}") | ||
| def search_addresses_by_tag(self, tag_id: str, tag_value: str, chain_id: str = None, limit: int = 1000) -> dict: | ||
| """ | ||
| Search for all addresses that have a specific tag_id=tag_value pair. Requires API key authentication. | ||
| Args: | ||
| tag_id (str): The tag key (e.g., 'usage_category') [required] | ||
| tag_value (str): The tag value (e.g., 'dex') [required] | ||
| chain_id (str): Optional chain_id filter (e.g., 'eip155:8453') | ||
| limit (int): Max number of addresses to return (1-1000). Default: 1000 | ||
| Returns: | ||
| dict: Response with tag_id, tag_value, count, and results array | ||
| Raises: | ||
| ValueError: If api_key is not provided | ||
| """ | ||
| if not self.api_key: | ||
| raise ValueError( | ||
| "API key is required for this endpoint. This is a protected endpoint that requires authentication.\n" | ||
| "Protected endpoints: get_labels, get_labels_bulk, search_addresses_by_tag, get_attester_analytics\n" | ||
| "Please provide an api_key parameter when calling this method.\n" | ||
| "You can obtain a free API key at https://openlabelsinitiative.org/signup" | ||
| ) | ||
| url = f"{self.api_url}/addresses/search" | ||
| headers = {'X-API-Key': self.api_key} | ||
| params = { | ||
| 'tag_id': tag_id, | ||
| 'tag_value': tag_value | ||
| } | ||
| if chain_id: | ||
| params['chain_id'] = chain_id | ||
| if limit: | ||
| params['limit'] = limit | ||
| response = requests.get(url, params=params, headers=headers) | ||
| if response.status_code == 200: | ||
| return response.json() | ||
| else: | ||
| raise Exception(f"Failed to search addresses by tag: {response.status_code} - {response.text}") | ||
| def get_attester_analytics(self, chain_id: str = None, limit: int = 100) -> dict: | ||
| """ | ||
| Get analytics summary grouped by attester. Requires API key authentication. | ||
| Returns count of labels and unique attestations per attester. | ||
| Args: | ||
| chain_id (str): Optional chain_id filter (e.g., 'eip155:8453') | ||
| limit (int): Number of rows to return (1-100). Default: 100 | ||
| Returns: | ||
| dict: Response with count and results array containing attester, label_count, and unique_attestations | ||
| Raises: | ||
| ValueError: If api_key is not provided | ||
| """ | ||
| if not self.api_key: | ||
| raise ValueError( | ||
| "API key is required for this endpoint. This is a protected endpoint that requires authentication.\n" | ||
| "Protected endpoints: get_labels, get_labels_bulk, search_addresses_by_tag, get_attester_analytics\n" | ||
| "Please provide an api_key parameter when calling this method.\n" | ||
| "You can obtain a free API key at https://openlabelsinitiative.org/signup" | ||
| ) | ||
| url = f"{self.api_url}/analytics/attesters" | ||
| headers = {'X-API-Key': self.api_key} | ||
| params = {} | ||
| if chain_id: | ||
| params['chain_id'] = chain_id | ||
| if limit: | ||
| params['limit'] = limit | ||
| response = requests.get(url, params=params, headers=headers) | ||
| if response.status_code == 200: | ||
| return response.json() | ||
| else: | ||
| raise Exception(f"Failed to get attester analytics: {response.status_code} - {response.text}") |
| import networkx as nx | ||
| class UtilsTrust: | ||
| def __init__(self, oli_client): | ||
| """ | ||
| Initialize the UtilsTrust with an OLI client. | ||
| Args: | ||
| oli_client: The OLI client instance | ||
| """ | ||
| self.oli = oli_client | ||
| # init trust variables | ||
| try: | ||
| self.raw_trust_lists = self.get_trust_lists_from_api() | ||
| self.TrustGraph = self.build_trust_graph(self.raw_trust_lists) | ||
| self.trust_table = self.compute_trust_table(self.TrustGraph, source_node=self.oli.source_address) | ||
| except: | ||
| self.raw_trust_lists = {} | ||
| self.trust_table = {} | ||
| self.TrustGraph = nx.MultiDiGraph() | ||
| # computes the trust table for a given source node | ||
| def compute_trust_table(self, TrustGraph: nx.MultiDiGraph, source_node: str=None) -> dict: | ||
| """ | ||
| Compute the full trust table for the source node in the trust graph. | ||
| """ | ||
| # get all tag_id & chain_id topics | ||
| tags_id = set() | ||
| chain_ids = set() | ||
| for _, _, edge_data in TrustGraph.edges(data=True): | ||
| tags_id.add(edge_data['topics']['tag_id']) | ||
| chain_ids.add(edge_data['topics']['chain_id']) | ||
| # resulting trust table | ||
| trust_table = {} | ||
| # Compute trust for all combinations | ||
| for tag_id_val in tags_id: | ||
| for chain_id_val in chain_ids: | ||
| query = {'tag_id': tag_id_val, 'chain_id': chain_id_val} | ||
| scores = self._dijkstra_trust(TrustGraph, source_node.lower(), query) | ||
| for target_node, score in scores.items(): | ||
| #key = (source_node, target_node, tag_id_val, chain_id_val) | ||
| key = (target_node, tag_id_val, chain_id_val) | ||
| trust_table[key] = score | ||
| # Remove redundant entries | ||
| trust_table = self._remove_redundant_entries(trust_table) | ||
| # Keep entries ordered by highest confidence first for predictable consumption | ||
| trust_table = dict(sorted(trust_table.items(), key=lambda item: item[1], reverse=True)) | ||
| return trust_table | ||
| # Build trust graph from raw trust lists | ||
| def build_trust_graph(self, raw_trust_lists) -> bool: | ||
| """ | ||
| Build the trust graph (TrustGraph) based on all trust lists stored in self.raw_trust_lists. | ||
| """ | ||
| # Keeps track of all unique nodes in the trust graph (trust list attesters + attestation attesters) | ||
| nodes = [] | ||
| # Topics | ||
| edges = [] | ||
| for address, tl in raw_trust_lists.items(): | ||
| if address.lower() not in nodes: | ||
| nodes.append(address.lower()) | ||
| for t in tl['attesters']: | ||
| if 'filters' not in t: | ||
| # Add trusted attesters (who have no filters) e.g. {'attester': '0xC139d50144Ee873c8577d682628E045dECe6040E', 'confidence': 0.8} | ||
| edges += [(address.lower(), t['address'].lower(), float(t['confidence']), {'chain_id': '*', 'tag_id': '*'})] | ||
| if t['address'].lower() not in nodes: | ||
| nodes.append(t['address'].lower()) | ||
| elif 'filters' in t: | ||
| # Add trusted attesters (with filters) e.g. {'attester': '0x10dBAc4d35f4aD47E85f70c74e4449c632EC4047', 'filters': [{'tag_id': 'is_contract', 'chain_id': 'eip155:48900', 'confidence': 0.9} | ||
| for f in t['filters']: | ||
| edges += [(address.lower(), t['address'].lower(), float(f['confidence']), {'chain_id': f.get('chain_id', '*'), 'tag_id': f.get('tag_id', '*')})] | ||
| if t['address'].lower() not in nodes: | ||
| nodes.append(t['address'].lower()) | ||
| # Create multi directed graph | ||
| TrustGraph = nx.MultiDiGraph() | ||
| TrustGraph.add_nodes_from(nodes) | ||
| print(f"Building trust graph based on {len(raw_trust_lists)} trust list(s), {len(nodes)} node(s) and {len(edges)} edge(s).") | ||
| # Add edges with attributes | ||
| for src, dst, trust, topics in edges: | ||
| TrustGraph.add_edge(src, dst, trust=trust, topics=topics) | ||
| return TrustGraph | ||
| def get_trust_lists_from_api(self) -> dict: | ||
| """ | ||
| Fetch trust lists (that are valid) from the OLI API and return them as a dictionary to be saved as 'oli.trust.raw_trust_lists'. | ||
| """ | ||
| trust_lists = {} | ||
| for tl in self.oli.api.get_trust_lists(order='asc')['trust_lists']: | ||
| if tl['revoked'] == False and self.oli.validate_trust_list(tl['owner_name'], tl['attesters'], tl['attestations']): | ||
| trust_lists[tl['attester']] = { | ||
| 'owner_name': tl['owner_name'], | ||
| 'attesters': tl['attesters'], | ||
| 'attestations': tl['attestations'] | ||
| } | ||
| return trust_lists | ||
| def add_trust_list(self, owner_name: str, attesters: list, attestations: list, attester_address: str) -> bool: | ||
| """ | ||
| Add (or overwrite) a trust list to the OLI trust_list variable and rebuild the trust graph. | ||
| Args: | ||
| owner_name (str): The name of the owner of the trust list | ||
| attesters (list): List of attesters in the trust list | ||
| attestations (list): List of attestations in the trust list | ||
| attester_address (str): The address of the attester providing the trust list | ||
| Returns: | ||
| bool: True if the trust list was added successfully | ||
| """ | ||
| try: | ||
| success = self.oli.validate_trust_list(owner_name, attesters, attestations) | ||
| if success: | ||
| self.raw_trust_lists[attester_address] = { | ||
| 'owner_name': owner_name, | ||
| 'attesters': attesters, | ||
| 'attestations': attestations | ||
| } | ||
| self.TrustGraph = self.build_trust_graph(self.raw_trust_lists) | ||
| except: | ||
| raise ValueError("Trust list validation failed or 'attester_address' invalid. No trust list added to TrustGraph. See validation errors above.") | ||
| return True | ||
| # Dijkstra with no specificity logic | ||
| def _dijkstra_trust(self, G: nx.MultiDiGraph, source: str, query_topics: dict) -> dict: | ||
| trust_scores = {} | ||
| visited = set() | ||
| pq = [(-1.0, source)] | ||
| while pq: | ||
| neg_trust, node = min(pq, key=lambda x: x[0]) | ||
| pq.remove((neg_trust, node)) | ||
| if node in visited: | ||
| continue | ||
| visited.add(node) | ||
| current_trust = -neg_trust | ||
| for neighbor in G.successors(node): | ||
| for key, edge_data in G[node][neighbor].items(): | ||
| if self._topics_match(edge_data['topics'], query_topics): | ||
| new_trust = current_trust * edge_data['trust'] | ||
| if neighbor not in trust_scores or new_trust > trust_scores[neighbor]: | ||
| trust_scores[neighbor] = new_trust | ||
| pq.append((-new_trust, neighbor)) | ||
| return trust_scores | ||
| # Remove entries subsumed by more general entries with >= score | ||
| def _remove_redundant_entries(self, trust_table: dict) -> dict: | ||
| cleaned = {} | ||
| by_pair = {} | ||
| for (tgt, tag, chain), score in trust_table.items(): | ||
| by_pair.setdefault(tgt, []).append(((tag, chain), score)) | ||
| for tgt, entries in by_pair.items(): | ||
| for (tag1, chain1), score1 in entries: | ||
| is_redundant = False | ||
| for (tag2, chain2), score2 in entries: | ||
| if (tag1, chain1) == (tag2, chain2): | ||
| continue | ||
| # Is entry2 strictly more general than entry1? | ||
| tag_more_general = (tag2 == '*' < tag1 != '*') | ||
| chain_more_general = (chain2 == '*' < chain1 != '*') | ||
| if (tag_more_general or chain_more_general) and score2 >= score1: | ||
| is_redundant = True | ||
| break | ||
| if not is_redundant: | ||
| cleaned[(tgt, tag1, chain1)] = score1 | ||
| return cleaned | ||
| # Do topics match function | ||
| def _topics_match(self, edge_topics: dict, query_topics: dict) -> bool: | ||
| wildcards = {'*'} | ||
| for dim, query_val in query_topics.items(): | ||
| edge_val = edge_topics.get(dim) | ||
| if edge_val not in wildcards and edge_val != query_val: | ||
| return False | ||
| return True |
| import json | ||
| import pandas as pd | ||
| import networkx as nx | ||
| class UtilsData: | ||
| def __init__(self, oli_client): | ||
| """ | ||
| Initialize the UtilsData with an OLI client. | ||
| Args: | ||
| oli_client: The OLI client instance | ||
| """ | ||
| self.oli = oli_client | ||
| # get confidence scores | ||
| def get_confidence(self, attester: str, tag_id: str, chain_id: str) -> float: | ||
| """ | ||
| Get the confidence score for a given attester, tag_id and chain_id from the trust table. | ||
| Args: | ||
| attester (str): Attester address | ||
| tag_id (str): Tag ID | ||
| chain_id (str): Chain ID | ||
| Returns: | ||
| float: Confidence score or -1 if no score was able to be assigned | ||
| """ | ||
| # raise ValueError if trust table is empty | ||
| if self.oli.trust.trust_table == {}: | ||
| raise ValueError("Trust table is empty. Please use 'oli.set_trust_node(source_address)' function to set the source node, which will compute your trust table.") | ||
| # Checksum the attester address | ||
| attester = attester.lower() | ||
| # Iterate through self.oli.trust.trust_table in order (is sorted by confidence) | ||
| for (t_attester, t_tag, t_chain), confidence in self.oli.trust.trust_table.items(): | ||
| # Check if this entry matches (with wildcard support) | ||
| if (t_attester.lower() == attester and (t_tag == tag_id or t_tag == '*') and (t_chain == chain_id or t_chain == '*')): | ||
| return confidence | ||
| return -1 |
+179
-56
| Metadata-Version: 2.4 | ||
| Name: oli-python | ||
| Version: 1.2.1 | ||
| Version: 2.0.0 | ||
| Summary: Python SDK for interacting with the Open Labels Initiative; A standard, registry and trust layer for EVM address labels. | ||
| Home-page: https://github.com/openlabelsinitiative/oli-python | ||
| Author: Lorenz Lehmann | ||
| Author-email: lorenz@growthepie.xyz | ||
| Author-email: lorenz@growthepie.com | ||
| Classifier: Programming Language :: Python :: 3 | ||
@@ -28,6 +28,8 @@ Classifier: License :: OSI Approved :: MIT License | ||
| # OLI Python Package | ||
| # OLI Python SDK | ||
| Python SDK for interacting with the Open Labels Initiative; A framework for address labels in the blockchain space. Read & write labels into the OLI Label Pool, check your labels for OLI compliance. | ||
| **`oli-python`** is the official Python SDK for the **[Open Labels Initiative (OLI)](https://github.com/openlabelsinitiative/OLI)**, a framework for standardized and trusted blockchain address labels. | ||
| This SDK lets you read, write, validate, and revoke address labels within the **OLI Label Pool** and compute confidence scores based on attesters' relationships within the **OLI Label Trust**. | ||
| ## Installation | ||
@@ -39,3 +41,3 @@ | ||
| ### Submitting New Labels | ||
| ## Initialization | ||
@@ -47,87 +49,208 @@ ```python | ||
| # Initialize the client | ||
| # Make sure to pull in your private key from an .env file | ||
| oli = OLI(private_key=os.environ['OLI_PRIVATE_KEY'], is_production=True) | ||
| oli = OLI( | ||
| private_key=os.getenv("PRIVATE_KEY"), | ||
| api_key=os.getenv("OLI_API_KEY") | ||
| ) | ||
| ``` | ||
| # Create a label | ||
| **Setup Instructions:** | ||
| * Create a `.env` file to store your private variables | ||
| * Load an EOA wallet by setting the `private_key` variable | ||
| * The EOA wallet is used to track your reputation within OLI | ||
| * To access all advanced API endpoints, pass an `api_key` variable. You can get your free API key from **[here](http://openlabelsinitiative.org/developer)** | ||
| **Additional Options:** | ||
| You can also set the `chain` parameter to specify which EAS contracts location to use and the `custom_rpc_url` variable when initializing. | ||
| ## Submit Labels into the OLI Label Pool | ||
| ### Single Attestation | ||
| ```python | ||
| # Define your OLI compliant attestation | ||
| address = "0x9438b8B447179740cD97869997a2FCc9b4AA63a2" | ||
| chain_id = "eip155:1" # Ethereum Mainnet | ||
| chain_id = "eip155:1" # Ethereum mainnet | ||
| tags = { | ||
| "contract_name": "growthepie donation address", | ||
| "is_eoa": True, | ||
| "owner_project": "growthepie" | ||
| "owner_project": "growthepie", | ||
| } | ||
| # Validate if your label is OLI compliant | ||
| possible_to_attest = oli.validate_label_correctness(address, chain_id, tags) | ||
| print(f"You can attest your label: {possible_to_attest}") | ||
| # (Optional) Validate your attestation before submission | ||
| is_valid = oli.validate_label(address, chain_id, tags) | ||
| print("Your attestation is OLI compliant:", is_valid) | ||
| # Submit a label as an offchain attestation | ||
| response = oli.submit_offchain_label(address, chain_id, tags) | ||
| print(f"Label submitted offchain with response: {response.text}") | ||
| # Submit label to the OLI Label Pool | ||
| response = oli.submit_label(address, chain_id, tags) | ||
| print(response) | ||
| ``` | ||
| # Submit a label as an onchain attestation | ||
| tx_hash, uid = oli.submit_onchain_label(address, chain_id, tags) | ||
| print(f"Label submitted onchain with hash: {tx_hash} and uid: {uid}") | ||
| ### Bulk Attestation | ||
| # Batch submit multiple labels as one onchain attestation | ||
| labels = [ | ||
| {'address': address, 'chain_id': chain_id, 'tags': tags}, | ||
| {'address': address, 'chain_id': chain_id, 'tags': tags} | ||
| ```python | ||
| attestations = [ | ||
| {"address": address, "chain_id": chain_id, "tags": tags}, | ||
| {"address": address, "chain_id": chain_id, "tags": tags}, | ||
| ] | ||
| tx_hash, uids = oli.submit_multi_onchain_labels(labels) | ||
| print(f"Labels batch submitted onchain with transaction hash: {tx_hash} and uids: {uids}") | ||
| # Revoke an attestation (revoking onchain attestations here) | ||
| trx_hash = oli.revoke_attestation(uid, onchain=True) | ||
| print(f"Label {uid} revoked with hash: {trx_hash}") | ||
| response = oli.submit_label_bulk(attestations) | ||
| print(response) | ||
| ``` | ||
| # Revoke multiple attestations (revoking onchain attestations here) | ||
| trx_hash, count = oli.multi_revoke_attestations(uids, onchain=True) | ||
| print(f"Labels batch revoked with hash: {trx_hash}") | ||
| ## Revoke Attestations | ||
| **Note:** All revocations are onchain transactions and therefore require you to have ETH on Base for gas in your wallet. | ||
| ```python | ||
| # Revoke a single attestation | ||
| oli.revoke_by_uid(uid="0x...") | ||
| # Bulk revocation | ||
| uids = ["0x1...", "0x2..."] | ||
| oli.revoke_bulk_by_uids(uids) | ||
| ``` | ||
| ### Querying Existing Labels | ||
| ## Reading the OLI Label Pool | ||
| ### Get Raw Attestations | ||
| ```python | ||
| from oli import OLI | ||
| import os | ||
| response = oli.get_attestations() | ||
| print(response) | ||
| ``` | ||
| # Initialize the client (read mode only doesn't require a private key) | ||
| oli = OLI() | ||
| **Available filters:** uid, attester, recipient, schema_info, since, order, limit | ||
| # Query attestations for a specific address | ||
| result = oli.graphql_query_attestations(address=address) | ||
| print(result) | ||
| ### Get Labels for an Address | ||
| # Download parquet export of raw attestations | ||
| oli.get_full_raw_export_parquet() | ||
| ```python | ||
| # All labels for an address | ||
| response = oli.get_labels(address = "0x9438b8B447179740cD97869997a2FCc9b4AA63a2") | ||
| print(response) | ||
| ``` | ||
| # Download parquet export of decoded attestations | ||
| oli.get_full_decoded_export_parquet() | ||
| **Available filters:** address, chain_id, limit, include_all | ||
| ### Bulk Get Labels for Addresses | ||
| ```python | ||
| response = ["0x9438b8B447179740cD97869997a2FCc9b4AA63a2", "0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24"] | ||
| bulk = oli.get_labels_bulk(response) | ||
| print(bulk) | ||
| ``` | ||
| **Available filters:** addresses, chain_id, limit_per_address, include_all | ||
| ## Wallet Requirements | ||
| ### Get Addresses Based on a Tag | ||
| The [OLI Label Pool](https://github.com/openlabelsinitiative/OLI/tree/main/2_label_pool) lives on Base as an [Ethereum Attestation schema](https://base.easscan.org/schema/view/0xb763e62d940bed6f527dd82418e146a904e62a297b8fa765c9b3e1f0bc6fdd68). | ||
| ```python | ||
| response = oli.search_addresses_by_tag( | ||
| tag_id="owner_project", | ||
| tag_value="growthepie" | ||
| ) | ||
| print(response) | ||
| ``` | ||
| Make sure your wallet contains ETH on **Base Mainnet** to pay for onchain transaction (including offchain revocations). Offchain attestations are free. | ||
| **Available filters:** tag_id, tag_value, chain_id, limit | ||
| For testing purposes, you can use Base Sepolia Testnet by setting `is_production=False` when initializing the client. | ||
| ### Attester Leaderboard | ||
| ## Features | ||
| ```python | ||
| chain_id = "eip155:1" | ||
| stats = oli.get_attester_analytics(chain_id) | ||
| print(stats) | ||
| ``` | ||
| - Submit onchain (single or batch) and offchain (single) labels into the OLI Label Pool | ||
| - Revoke your own labels (single or batch) | ||
| - Validate if your label is OLI compliant | ||
| - Query attestations using GraphQL | ||
| - Download full dataset exports in Parquet format | ||
| ## OLI Label Trust | ||
| ## Documentation | ||
| ```python | ||
| # Set a new source node for trust propagation | ||
| trust_table = oli.set_trust_node("0xYourAddress") | ||
| print(oli.trust.trust_table) | ||
| For more details, see the [OLI Documentation](https://github.com/openlabelsinitiative/OLI). | ||
| # Import your trust list from YAML file | ||
| import yaml | ||
| with open('example_trust_list.yml', 'r') as file: | ||
| trust_list = yaml.safe_load(file) | ||
| owner_name = trust_list.get('owner_name') | ||
| attesters = trust_list.get('attesters', []) | ||
| attestations = trust_list.get('attestations', []) | ||
| # Check if the trust list is OLI compliant | ||
| is_valid = oli.validate_trust_list( | ||
| owner_name, | ||
| attesters, | ||
| attestations | ||
| ) | ||
| print("Your trust list is OLI compliant:", is_valid) | ||
| # Add a private trust list to the trust graph | ||
| oli.add_trust_list( | ||
| owner_name, | ||
| attesters, | ||
| attestations | ||
| ) | ||
| # Submit your trust list to the global trust graph | ||
| response = oli.submit_trust_list( | ||
| owner_name, | ||
| attesters, | ||
| attestations | ||
| ) | ||
| print(response) | ||
| ``` | ||
| Once you set your trust node or submit your trust list, the transitive trust algorithm can assign a trust score to all labels. Use the following endpoints, which now assign a confidence score to each label. You can also set `min_confidence` to filter based on confidence thresholds. | ||
| ### Get Trusted Labels for an Address | ||
| ```python | ||
| # All trusted labels for an address | ||
| response = oli.get_trusted_labels(address = "0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24") | ||
| print(response) | ||
| ``` | ||
| **Available filters:** address, chain_id, limit, include_all, min_confidence | ||
| ### Bulk Get Trusted Labels for Addresses | ||
| ```python | ||
| addresses = ["0x9438b8B447179740cD97869997a2FCc9b4AA63a2", "0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24"] | ||
| response = oli.get_trusted_labels_bulk(addresses) | ||
| print(response) | ||
| ``` | ||
| **Available filters:** addresses, chain_id, limit_per_address, include_all, min_confidence | ||
| ### Get Trusted Addresses Based on a Tag | ||
| ```python | ||
| response = oli.search_trusted_addresses_by_tag( | ||
| tag_id = "owner_project", | ||
| tag_value = "growthepie" | ||
| ) | ||
| print(response) | ||
| ``` | ||
| **Available filters:** tag_id, tag_value, chain_id, limit, min_confidence | ||
| ## OLI Label Pool Parquet Exports | ||
| growthepie provides dataset exports as Parquet files for all attestations inside the OLI Label Pool, updated daily: | ||
| ```python | ||
| oli.get_full_raw_export_parquet() | ||
| oli.get_full_decoded_export_parquet() | ||
| ``` | ||
| **Option:** Pass the filepath as a variable to store the export in a specific location. | ||
| ## OLI Documentation | ||
| * [Open Labels Initiative Docs](https://github.com/openlabelsinitiative/OLI) | ||
| * [OLI Website](https://www.openlabelsinitiative.org/) | ||
| ## License | ||
| MIT | ||
| MIT © Open Labels Initiative |
@@ -11,4 +11,5 @@ README.md | ||
| oli/data/__init__.py | ||
| oli/data/fetcher.py | ||
| oli/data/graphql.py | ||
| oli/data/api.py | ||
| oli/data/trust.py | ||
| oli/data/utils.py | ||
| oli_python.egg-info/PKG-INFO | ||
@@ -15,0 +16,0 @@ oli_python.egg-info/SOURCES.txt |
+0
-1
| from oli.core import OLI | ||
| __version__ = "0.1.0" | ||
| __all__ = ["OLI"] |
+138
-39
@@ -8,3 +8,3 @@ import time | ||
| class OffchainAttestations: | ||
| def __init__(self, oli_client): | ||
| def __init__(self, oli_client, private_key: str): | ||
| """ | ||
@@ -17,2 +17,3 @@ Initialize OffchainAttestations with an OLI client. | ||
| self.oli = oli_client | ||
| self._private_key = private_key | ||
@@ -33,8 +34,14 @@ def submit_offchain_label(self, address: str, chain_id: str, tags: dict, ref_uid: str="0x0000000000000000000000000000000000000000000000000000000000000000", retry: int=4): | ||
| """ | ||
| # fix simple formatting errors in tags | ||
| tags = self.oli.validator.fix_simple_tags_formatting(tags) | ||
| # in case tag definition fails to load, skip tag formatting and validation | ||
| if self.oli.tag_definitions is not None: | ||
| # Check all necessary input parameters | ||
| self.oli.validator.validate_label_correctness(address, chain_id, tags, ref_uid, auto_fix=False) | ||
| # fix simple formatting errors in tags | ||
| tags = self.oli.validator.fix_simple_tags_formatting(tags) | ||
| # Check all necessary input parameters | ||
| self.oli.validator.validate_label_correctness(address, chain_id, tags, ref_uid, auto_fix=False) | ||
| else: | ||
| print("Warning: OLI tag definitions not loaded, skipping tag formatting and validation. Please upgrade to the latest OLI version and ensure internet connectivity at initialization.") | ||
| # Encode the label data | ||
@@ -50,5 +57,13 @@ data = self.oli.utils_other.encode_label_data(chain_id, tags) | ||
| ) | ||
| # Convert numerical values to strings for JSON serialization | ||
| attestation["sig"]["message"]["time"] = str(attestation["sig"]["message"]["time"]) | ||
| attestation["sig"]["message"]["expirationTime"] = str(attestation["sig"]["message"]["expirationTime"]) | ||
| attestation["sig"]["domain"]["chainId"] = str(attestation["sig"]["domain"]["chainId"]) | ||
| # extract uid | ||
| uid = attestation["sig"]["uid"] | ||
| # Post to the API & retry if status code is not 200 | ||
| response = self.post_offchain_attestation(attestation) | ||
| response = self.oli.api.post_single_attestation(attestation) | ||
| n0 = retry | ||
@@ -58,28 +73,109 @@ while response.status_code != 200 and retry > 0: | ||
| time.sleep(2 ** (n0 - retry)) # exponential backoff | ||
| # rebuild the attestation (assigns new timestamp) to not get rate limited by EAS post endpoint | ||
| attestation = self.build_offchain_attestation( | ||
| recipient=address, | ||
| schema=self.oli.oli_label_pool_schema, | ||
| data=data, | ||
| ref_uid=ref_uid | ||
| ) | ||
| response = self.post_offchain_attestation(attestation) | ||
| response = self.oli.api.post_single_attestation(attestation) | ||
| # if it fails after all retries, raise an error | ||
| if response.status_code != 200: | ||
| raise Exception(f"Failed to submit offchain attestation to EAS API ipfs post endpoint after {n0} retries: {response.status_code} - {response.text}") | ||
| raise Exception(f"Failed to submit offchain attestation label to OLI API post endpoint 'attestation' after {n0} retries: {response.status_code} - {response.text}") | ||
| return response | ||
| return response, uid | ||
| def post_offchain_attestation(self, attestation: dict, filename: str="OLI.txt") -> Response: | ||
| def submit_offchain_labels_bulk(self, attestations: list, retry: int=4) -> dict: | ||
| """ | ||
| Post API an attestation to the EAS API. | ||
| Submit up to 1000 OLI labels as offchain attestations to the OLI Label Pool in bulk. | ||
| Args: | ||
| attestation (dict): The attestation package | ||
| filename (str): Custom filename | ||
| attestations (list): List of attestation objects (1-1000 items), each containing address, chain_id, tags (and optional ref_uid) | ||
| retry (int): Number of retries for the API post request to EAS ipfs | ||
| Returns: | ||
| dict: API response | ||
| dict: API request response | ||
| """ | ||
| signed_attestations = [] | ||
| uids = [] | ||
| for attestation in attestations: | ||
| address = attestation.get("address") | ||
| chain_id = attestation.get("chain_id") | ||
| tags = attestation.get("tags", None) # no tags means no attestation will be created | ||
| ref_uid = attestation.get("ref_uid", "0x0000000000000000000000000000000000000000000000000000000000000000") | ||
| if tags is not None: | ||
| # in case tag definition fails to load, skip tag formatting and validation | ||
| if self.oli.tag_definitions is not None: | ||
| # fix simple formatting errors in tags | ||
| tags = self.oli.validator.fix_simple_tags_formatting(tags) | ||
| # Check all necessary input parameters | ||
| self.oli.validator.validate_label_correctness(address, chain_id, tags, ref_uid, auto_fix=False) | ||
| else: | ||
| print("Warning: OLI tag definitions not loaded, skipping tag formatting and validation. Please upgrade to the latest OLI version and ensure internet connectivity at initialization.") | ||
| # Encode the label data | ||
| data = self.oli.utils_other.encode_label_data(chain_id, tags) | ||
| # Build the attestation | ||
| attestation = self.build_offchain_attestation( | ||
| recipient=address, | ||
| schema=self.oli.oli_label_pool_schema, | ||
| data=data, | ||
| ref_uid=ref_uid | ||
| ) | ||
| # Convert numerical values to strings for JSON serialization | ||
| attestation["sig"]["message"]["time"] = str(attestation["sig"]["message"]["time"]) | ||
| attestation["sig"]["message"]["expirationTime"] = str(attestation["sig"]["message"]["expirationTime"]) | ||
| attestation["sig"]["domain"]["chainId"] = str(attestation["sig"]["domain"]["chainId"]) | ||
| # extract uid | ||
| uid = attestation["sig"]["uid"] | ||
| uids.append(uid) | ||
| signed_attestations.append(attestation) | ||
| # Post to the API & retry if status code is not 200 | ||
| response = self.oli.api.post_bulk_attestations(signed_attestations) | ||
| n0 = retry | ||
| while response.status_code != 200 and retry > 0: | ||
| retry -= 1 | ||
| time.sleep(2 ** (n0 - retry)) # exponential backoff | ||
| response = self.oli.api.post_bulk_attestations(signed_attestations) | ||
| # if it fails after all retries, raise an error | ||
| if response.status_code != 200: | ||
| raise Exception(f"Failed to submit offchain attestation labels bulk to OLI API post endpoint 'attestation/bulk' after {n0} retries: {response.status_code} - {response.text}") | ||
| return response, uids | ||
| def submit_offchain_trust_list(self, owner_name: str='', attesters: list=[], attestations: list=[], retry: int=4) -> dict: | ||
| """ | ||
| Submit an OLI trust list as an offchain attestation to the OLI Trust List Pool. | ||
| Args: | ||
| owner (str): The address of the owner of the trust list | ||
| attesters (list): List of dictionaries containing attester information with trust scores and optional filters | ||
| attestations (list): List of dictionaries containing attestation information with trust scores | ||
| retry (int): Number of retries for the API post request to EAS ipfs | ||
| Returns: | ||
| dict: API request response | ||
| """ | ||
| # Validate the trust list | ||
| self.oli.validator.validate_trust_list_correctness(owner_name, attesters, attestations) | ||
| # Encode the label data | ||
| data = self.oli.utils_other.encode_list_data(owner_name, attesters, attestations) | ||
| # Build the attestation | ||
| recipient = "0x0000000000000000000000000000000000000000" | ||
| ref_uid = "0x0000000000000000000000000000000000000000000000000000000000000000" | ||
| attestation = self.build_offchain_attestation( | ||
| recipient=recipient, | ||
| schema=self.oli.oli_label_trust_schema, | ||
| data=data, | ||
| ref_uid=ref_uid | ||
| ) | ||
| # Convert numerical values to strings for JSON serialization | ||
@@ -89,16 +185,19 @@ attestation["sig"]["message"]["time"] = str(attestation["sig"]["message"]["time"]) | ||
| attestation["sig"]["domain"]["chainId"] = str(attestation["sig"]["domain"]["chainId"]) | ||
| # extract uid | ||
| uid = attestation["sig"]["uid"] | ||
| # Post to the API & retry if status code is not 200 | ||
| response = self.oli.api.post_trust_list(attestation) | ||
| n0 = retry | ||
| while response.status_code != 200 and retry > 0: | ||
| retry -= 1 | ||
| time.sleep(2 ** (n0 - retry)) # exponential backoff | ||
| response = self.oli.api.post_trust_list(attestation) | ||
| # Prepare payload for the API endpoint | ||
| payload = { | ||
| "filename": filename, | ||
| "textJson": json.dumps(attestation, separators=(',', ':')) | ||
| } | ||
| headers = { | ||
| "Content-Type": "application/json" | ||
| } | ||
| # Post the data to the API | ||
| response = requests.post(self.oli.eas_api_url, json=payload, headers=headers) | ||
| return response | ||
| # if it fails after all retries, raise an error | ||
| if response.status_code != 200: | ||
| raise Exception(f"Failed to submit offchain attestation trust list to OLI API post endpoint 'trust-list' after {n0} retries: {response.status_code} - {response.text}") | ||
| return response, uid | ||
@@ -226,3 +325,3 @@ def build_offchain_attestation(self, recipient: str, schema: str, data: str, ref_uid: str, revocable: bool=True, expiration_time: int=0) -> dict: | ||
| # Sign the transaction | ||
| signed_txn = self.oli.w3.eth.account.sign_transaction(transaction, private_key=self.oli.private_key) | ||
| signed_txn = self.oli.w3.eth.account.sign_transaction(transaction, private_key=self._private_key) | ||
@@ -275,3 +374,3 @@ # Send the transaction | ||
| # Sign the transaction | ||
| signed_txn = self.oli.w3.eth.account.sign_transaction(transaction, private_key=self.oli.private_key) | ||
| signed_txn = self.oli.w3.eth.account.sign_transaction(transaction, private_key=self._private_key) | ||
@@ -286,4 +385,4 @@ # Send the transaction | ||
| if txn_receipt.status == 1: | ||
| return f"0x{txn_hash.hex()}", len(uids) | ||
| return f"0x{txn_hash.hex()}" | ||
| else: | ||
| raise Exception(f"Transaction failed: {txn_receipt}") |
| class OnchainAttestations: | ||
| def __init__(self, oli_client): | ||
| def __init__(self, oli_client, private_key: str): | ||
| """ | ||
@@ -10,4 +10,5 @@ Initialize OnchainAttestations with an OLI client. | ||
| self.oli = oli_client | ||
| self.__private_key = private_key | ||
| def submit_onchain_label(self, address: str, chain_id: str, tags: dict, ref_uid: str="0x0000000000000000000000000000000000000000000000000000000000000000", gas_limit: int=0) -> tuple[str, str]: | ||
| def submit_onchain_label(self, address: str, chain_id: str, tags: dict, ref_uid: str="0x0000000000000000000000000000000000000000000000000000000000000000", gas_limit: int=-1) -> tuple[str, str]: | ||
| """ | ||
@@ -21,3 +22,3 @@ Submit an OLI label as an onchain attestation to the OLI Label Pool. | ||
| ref_uid (str): Reference UID | ||
| gas_limit (int): Gas limit for the transaction. If set to 0, the function will estimate the gas limit. | ||
| gas_limit (int): Gas limit for the transaction. If set to -1, the function will estimate the gas limit. | ||
@@ -28,8 +29,14 @@ Returns: | ||
| """ | ||
| # fix simple formatting errors in tags | ||
| tags = self.oli.validator.fix_simple_tags_formatting(tags) | ||
| # in case tag definition fails to load, skip tag formatting and validation | ||
| if self.oli.tag_definitions is not None: | ||
| # Check all necessary input parameters | ||
| self.oli.validator.validate_label_correctness(address, chain_id, tags, ref_uid, auto_fix=False) | ||
| # fix simple formatting errors in tags | ||
| tags = self.oli.validator.fix_simple_tags_formatting(tags) | ||
| # Check all necessary input parameters | ||
| self.oli.validator.validate_label_correctness(address, chain_id, tags, ref_uid, auto_fix=False) | ||
| else: | ||
| print("Warning: OLI tag definitions not loaded, skipping tag formatting and validation. Please upgrade to the latest OLI version and ensure internet connectivity at initialization.") | ||
| # Encode the label data | ||
@@ -65,3 +72,3 @@ data = self.oli.utils_other.encode_label_data(chain_id, tags) | ||
| # Sign the transaction with the private key | ||
| signed_txn = self.oli.w3.eth.account.sign_transaction(transaction, private_key=self.oli.private_key) | ||
| signed_txn = self.oli.w3.eth.account.sign_transaction(transaction, private_key=self.__private_key) | ||
@@ -82,4 +89,4 @@ # Send the transaction | ||
| raise Exception(f"Transaction failed onchain: {txn_receipt}") | ||
| def submit_multi_onchain_labels(self, labels: list, gas_limit: int=0) -> tuple[str, list]: | ||
| def submit_multi_onchain_labels(self, labels: list, gas_limit: int=-1) -> tuple[str, list]: | ||
| """ | ||
@@ -94,3 +101,3 @@ Batch submit multiple OLI labels as an onchain attestation to the OLI Label Pool. | ||
| ref_uid (str): Reference UID | ||
| gas_limit (int): Gas limit for one transaction to submit all labels passed, make sure to set it high enough for multiple attestations! If set to 0, the function will estimate the gas limit. | ||
| gas_limit (int): Gas limit for one transaction to submit all labels passed, make sure to set it high enough for multiple attestations! If set to -1, the function will estimate the gas limit. | ||
@@ -112,8 +119,14 @@ Returns: | ||
| # fix simple formatting errors in tags | ||
| label['tags'] = self.oli.validator.fix_simple_tags_formatting(label['tags']) | ||
| # in case tag definition fails to load, skip tag formatting and validation | ||
| if self.oli.tag_definitions is not None: | ||
| # fix simple formatting errors in tags | ||
| label['tags'] = self.oli.validator.fix_simple_tags_formatting(label['tags']) | ||
| # run checks on each label | ||
| self.oli.validator.validate_label_correctness(label['address'], label['chain_id'], label['tags'], auto_fix=False) | ||
| # run checks on each label | ||
| self.oli.validator.validate_label_correctness(label['address'], label['chain_id'], label['tags'], auto_fix=False) | ||
| else: | ||
| print("Warning: OLI tag definitions not loaded, skipping tag formatting and validation. Please upgrade to the latest OLI version and ensure internet connectivity at initialization.") | ||
| # check if ref_uid is provided | ||
@@ -159,3 +172,3 @@ if 'ref_uid' not in label: | ||
| # Sign the transaction with the private key | ||
| signed_txn = self.oli.w3.eth.account.sign_transaction(transaction, private_key=self.oli.private_key) | ||
| signed_txn = self.oli.w3.eth.account.sign_transaction(transaction, private_key=self.__private_key) | ||
@@ -179,3 +192,67 @@ # Send the transaction | ||
| return f"0x{txn_hash.hex()}", uids | ||
| def submit_onchain_trust_list(self, owner: str, trusted: list, untrusted: list, gas_limit: int=-1) -> tuple[str, str]: | ||
| """ | ||
| Submit an OLI trust list as an onchain attestation to the OLI Trust List Pool. | ||
| Args: | ||
| owner (str): The address of the owner of the trust list | ||
| trusted (list): List of dictionaries containing trusted addresses | ||
| untrusted (list): List of dictionaries containing untrusted addresses | ||
| gas_limit (int): Gas limit for the transaction. If set to -1, the function will estimate the gas limit. | ||
| Returns: | ||
| str: Transaction hash | ||
| str: UID of the attestation | ||
| """ | ||
| # Validate the trust list | ||
| self.oli.validator.validate_trust_list_correctness(owner, trusted, untrusted) | ||
| # Encode the label data | ||
| data = self.oli.utils_other.encode_list_data(owner, trusted, untrusted) | ||
| # Create the attestation | ||
| function = self.oli.eas.functions.attest({ | ||
| 'schema': self.oli.w3.to_bytes(hexstr=self.oli.oli_label_trust_schema), | ||
| 'data': { | ||
| 'recipient': "0x0000000000000000000000000000000000000000", # Trust lists are not tied to a specific address | ||
| 'expirationTime': 0, # never expires | ||
| 'revocable': True, # can be revoked | ||
| 'refUID': "0x0000000000000000000000000000000000000000000000000000000000000000", # no ref UID for trust lists | ||
| 'data': self.oli.w3.to_bytes(hexstr=data), | ||
| 'value': 0 | ||
| } | ||
| }) | ||
| # Define the transaction parameters | ||
| tx_params = { | ||
| 'chainId': self.oli.rpc_chain_number, | ||
| 'gasPrice': self.oli.w3.eth.gas_price, | ||
| 'nonce': self.oli.w3.eth.get_transaction_count(self.oli.address), | ||
| } | ||
| # Estimate gas if no limit provided | ||
| tx_params = self.oli.utils_other.estimate_gas_limit(function, tx_params, gas_limit) | ||
| # Build the transaction to attest one label | ||
| transaction = function.build_transaction(tx_params) | ||
| # Sign the transaction with the private key | ||
| signed_txn = self.oli.w3.eth.account.sign_transaction(transaction, private_key=self.__private_key) | ||
| # Send the transaction | ||
| try: | ||
| txn_hash = self.oli.w3.eth.send_raw_transaction(signed_txn.raw_transaction) | ||
| except Exception as e: | ||
| raise Exception(f"Failed to send transaction to mempool: {e}") | ||
| # Wait for the transaction receipt | ||
| txn_receipt = self.oli.w3.eth.wait_for_transaction_receipt(txn_hash) | ||
| # Check if the transaction was successful | ||
| if txn_receipt.status == 1: | ||
| return f"0x{txn_hash.hex()}", f"0x{txn_receipt.logs[0].data.hex()}" | ||
| else: | ||
| raise Exception(f"Transaction failed onchain: {txn_receipt}") | ||
| def revoke_attestation(self, uid_hex: str, gas_limit: int=200000) -> str: | ||
@@ -214,3 +291,3 @@ """ | ||
| # Sign the transaction | ||
| signed_txn = self.oli.w3.eth.account.sign_transaction(transaction, private_key=self.oli.private_key) | ||
| signed_txn = self.oli.w3.eth.account.sign_transaction(transaction, private_key=self.__private_key) | ||
@@ -270,3 +347,3 @@ # Send the transaction | ||
| # Sign the transaction | ||
| signed_txn = self.oli.w3.eth.account.sign_transaction(transaction, private_key=self.oli.private_key) | ||
| signed_txn = self.oli.w3.eth.account.sign_transaction(transaction, private_key=self.__private_key) | ||
@@ -281,4 +358,4 @@ # Send the transaction | ||
| if txn_receipt.status == 1: | ||
| return f"0x{txn_hash.hex()}", len(uids) | ||
| return f"0x{txn_hash.hex()}" | ||
| else: | ||
| raise Exception(f"Transaction failed: {txn_receipt}") |
@@ -35,2 +35,23 @@ import json | ||
| def encode_list_data(self, owner: str, trusted: list, untrusted: list) -> str: | ||
| """ | ||
| Encode trust list data in the OLI format. | ||
| Args: | ||
| owner (str): Owner name of the trust list | ||
| trusted (dict): Dictionary of trusted entities | ||
| untrusted (dict): Dictionary of untrusted entities | ||
| Returns: | ||
| str: Hex-encoded ABI data | ||
| """ | ||
| # Convert list to JSON strings if needed | ||
| if isinstance(trusted, list): | ||
| trusted = json.dumps(trusted) | ||
| if isinstance(untrusted, list): | ||
| untrusted = json.dumps(untrusted) | ||
| # ABI encode the data | ||
| encoded_data = encode(['string', 'string', 'string'], [owner, trusted, untrusted]) | ||
| return f"0x{encoded_data.hex()}" | ||
| def estimate_gas_limit(self, function, tx_params: dict, gas_limit: int) -> dict: | ||
@@ -49,3 +70,3 @@ """ | ||
| try: | ||
| if gas_limit == 0: | ||
| if gas_limit == -1: | ||
| # Estimate gas with a buffer (e.g., 10% more than the estimate) | ||
@@ -52,0 +73,0 @@ estimated_gas = function.estimate_gas(tx_params) |
@@ -18,2 +18,8 @@ class UtilsValidator: | ||
| ] | ||
| # URLs for helpful resources | ||
| self.url_1_label_schema = "https://github.com/openlabelsinitiative/OLI/tree/main/1_label_schema" | ||
| self.url_2_label_pool = "https://github.com/openlabelsinitiative/OLI/tree/main/2_label_pool" | ||
| self.url_3_label_trust = "https://github.com/openlabelsinitiative/OLI/tree/main/3_label_trust" | ||
| self.url_tag_definitions = "https://github.com/openlabelsinitiative/OLI/blob/main/1_label_schema/tags/tag_definitions.yml" | ||
| self.url_caip2_format = "https://docs.portalhq.io/resources/chain-id-formatting" | ||
@@ -58,3 +64,3 @@ def fix_simple_tags_formatting(self, tags: dict) -> dict: | ||
| """ | ||
| Validates if the label is compliant with the OLI Data Model. See OLI Github documentation for more details: https://github.com/openlabelsinitiative/OLI | ||
| Validates if the label is compliant with the OLI Label Schema. See OLI Github documentation for more details: https://github.com/openlabelsinitiative/OLI/tree/main/1_label_schema | ||
@@ -77,3 +83,25 @@ Args: | ||
| return True | ||
| def validate_trust_list_correctness(self, owner_name: str, attesters: list, attestations: list) -> bool: | ||
| """ | ||
| Validates if the trust list is compliant with the OLI Label Trust definitions. See OLI Github documentation for more details: https://github.com/openlabelsinitiative/OLI/tree/main/3_label_trust | ||
| Args: | ||
| owner (str): Owner name of the trust list | ||
| attesters (list): List of attester information with confidence scores and optional filters | ||
| attestations (list): List of attestation information with confidence scores | ||
| Returns: | ||
| bool: True if the trust list is correct, False otherwise | ||
| """ | ||
| if owner_name.isascii() != True: | ||
| print(owner_name) | ||
| raise ValueError("Owner name must only contain ASCII characters") | ||
| elif len(owner_name) < 3 or len(owner_name) > 100: | ||
| print(owner_name) | ||
| raise ValueError("Owner name must be between 3 and 100 characters long") | ||
| self.validate_attesters_list(attesters) | ||
| self.validate_attestations_list(attestations) | ||
| return True | ||
| def validate_chain_id(self, chain_id: str) -> bool: | ||
@@ -107,3 +135,3 @@ """ | ||
| print(f"Unsupported chain ID format: {chain_id}") | ||
| raise ValueError("Chain ID must be in CAIP-2 format (e.g., Base -> 'eip155:8453'), see this guide on CAIP-2: https://docs.portalhq.io/resources/chain-id-formatting") | ||
| raise ValueError(f"Chain ID must be in CAIP-2 format (e.g., Base -> 'eip155:8453'), see this guide on CAIP-2: {self.url_caip2_format}") | ||
@@ -144,3 +172,3 @@ def validate_address(self, address: str) -> bool: | ||
| print(tags) | ||
| raise ValueError("Tags must be a dictionary with OLI compliant tags (e.g., {'contract_name': 'example', 'is_eoa': True})") | ||
| raise ValueError(f"Tags must be a dictionary with OLI compliant tags (e.g., {{'contract_name': 'example', 'is_eoa': True}}). See for example: {self.url_1_label_schema}") | ||
@@ -152,3 +180,3 @@ # Check each tag_id in the dictionary # TODO: redo this with tag_definitions 2.0 and schema, should be more efficient | ||
| if tag_id not in self.oli.tag_ids: | ||
| print(f"WARNING: Tag tag_id '{tag_id}' is not an official OLI tag. Please check 'oli.tag_definitions' or https://github.com/openlabelsinitiative/OLI/blob/main/1_label_schema/tags/tag_definitions.yml.") | ||
| print(f"WARNING: Tag tag_id '{tag_id}' is not an official OLI tag. Please check 'oli.tag_definitions' or {self.url_tag_definitions}.") | ||
@@ -221,2 +249,112 @@ # Check if the tag_id is in the correct format. So far implemented [boolean, string, integer, list, float, string(42), string(66), date (YYYY-MM-DD HH:MM:SS)] | ||
| print(ref_uid) | ||
| raise ValueError("Ref_uid must be a valid UID in hex format, leave empty if not used") | ||
| raise ValueError("Ref_uid must be a valid UID in hex format") | ||
| def validate_attesters_list(self, attesters: list) -> bool: | ||
| """ | ||
| Validates if the attester list is in the correct format. | ||
| Args: | ||
| attesters (list): Attester list to check | ||
| Returns: | ||
| bool: True if correct, throws error otherwise | ||
| """ | ||
| if not isinstance(attesters, list): | ||
| raise ValueError("Attesters list must be a list.") | ||
| for item in attesters: | ||
| # Validate attester | ||
| if 'address' in item: | ||
| # check attester address | ||
| self.validate_address(item['address']) | ||
| # Check attester item | ||
| self.validate_attester_item(item) | ||
| else: | ||
| print(item) | ||
| raise ValueError(f"Each attester entry must have an 'address' key. See for example: {self.url_3_label_trust}") | ||
| return True | ||
| def validate_attestations_list(self, attestations: list) -> bool: | ||
| """ | ||
| Validates if the attestations list is in the correct format. | ||
| Args: | ||
| untrusted (list): Untrusted list to check | ||
| Returns: | ||
| bool: True if correct, throws error otherwise | ||
| """ | ||
| if not isinstance(attestations, list): | ||
| raise ValueError("Attestations list must be a list.") | ||
| for item in attestations: | ||
| # Validate attestation | ||
| if 'uid' in item: | ||
| # check attestation UID | ||
| self.validate_ref_uid(item['uid']) | ||
| # check attestation item | ||
| self.validate_attestation_item(item) | ||
| else: | ||
| print(item) | ||
| raise ValueError(f"Each attestation entry must have an 'uid' key. See for example: {self.url_3_label_trust}") | ||
| return True | ||
| def validate_attester_item(self, item: dict) -> bool: | ||
| """ | ||
| Validates if an attester item is in the correct format. | ||
| Args: | ||
| item (dict): Attester item to check | ||
| Returns: | ||
| bool: True if valid, throws error otherwise | ||
| """ | ||
| # Check if confidence key is present | ||
| if 'confidence' in item: | ||
| if 'filters' in item: | ||
| # if 'confidence' is assigned to an attester, then specific confidence scores based on 'filters' are not allowed | ||
| print(item) | ||
| raise ValueError(f"If a 'confidence' key is assigned to an attester, specific confidence scores based on 'filters' are not allowed. Please remove 'filters'. See for example: {self.url_3_label_trust}") | ||
| elif item['confidence'] < 0 or item['confidence'] > 1: | ||
| print(item) | ||
| raise ValueError("'confidence' scores must be between 0 and 1") | ||
| elif 'filters' in item: | ||
| # make sure tag_id or chain_id is present in each entry in filters | ||
| if isinstance(item['filters'], list): | ||
| for tag in item['filters']: | ||
| if 'tag_id' not in tag and 'chain_id' not in tag: | ||
| print(item['filters']) | ||
| raise ValueError(f"Each filter must have a at least a key for 'tag_id' or 'chain_id'. See for example: {self.url_3_label_trust}") | ||
| elif 'confidence' not in tag: | ||
| print(item['filters']) | ||
| raise ValueError(f"Each filter must have a key called 'confidence'. See for example: {self.url_3_label_trust}") | ||
| elif tag['confidence'] < 0 or tag['confidence'] > 1: | ||
| print(item['filters']) | ||
| raise ValueError("Each 'confidence' score must be between 0 and 1") | ||
| else: | ||
| print(item['filters']) | ||
| raise ValueError(f"'filters' must be a list of dictionaries with 'tag_id' or 'chain_id' filters and a 'confidence' score between 0 and 1. See for example: {self.url_3_label_trust}") | ||
| else: | ||
| print(item) | ||
| raise ValueError(f"Each attester entry must have either have a 'confidence' or 'filters' key. See for example: {self.url_3_label_trust}") | ||
| return True | ||
| def validate_attestation_item(self, item: dict) -> bool: | ||
| """ | ||
| Validates if an attestation item is in the correct format. | ||
| Args: | ||
| item (dict): Attestation item to check | ||
| Returns: | ||
| bool: True if valid, throws error otherwise | ||
| """ | ||
| if 'confidence' not in item: | ||
| print(item) | ||
| raise ValueError(f"Each attestation entry with a 'uid' key must also have a 'confidence' key. See for example: {self.url_3_label_trust}") | ||
| elif item['confidence'] < 0 or item['confidence'] > 1: | ||
| print(item) | ||
| raise ValueError(f"Confidence must be between 0 and 1.") | ||
| return True |
+311
-90
| from web3 import Web3 | ||
| import eth_account | ||
| import pandas as pd | ||
| import os | ||
@@ -11,7 +12,27 @@ from eth_keys import keys | ||
| from oli.attestation.offchain import OffchainAttestations | ||
| from oli.data.fetcher import DataFetcher | ||
| from oli.data.graphql import GraphQLClient | ||
| from oli.data.api import API | ||
| from oli.data.utils import UtilsData | ||
| from oli.data.trust import UtilsTrust | ||
| from dataclasses import dataclass | ||
| @dataclass | ||
| class PostResponse: | ||
| """Standardized response for all POST operations""" | ||
| success: bool | ||
| onchain: bool | ||
| transaction_hash: str = None | ||
| uid: str = None | ||
| uids: list = None | ||
| eas_schema_chain: str = None # only for onchain operations | ||
| eas_schema: str = None | ||
| status: str = None | ||
| accepted: int = None # for bulk operations | ||
| duplicates: int = None # for bulk operations | ||
| failed_validation: list = None # for bulk operations | ||
| error: str = None | ||
| def to_dict(self) -> dict: | ||
| return {k: v for k, v in self.__dict__.items() if v is not None} | ||
| class OLI: | ||
| def __init__(self, private_key: str=None, is_production: bool=True, custom_rpc_url: str=None) -> None: | ||
| def __init__(self, private_key: str=None, chain: str='Base', api_key: str=None, custom_rpc_url: str=None) -> None: | ||
| """ | ||
@@ -22,3 +43,3 @@ Initialize the OLI API client. | ||
| private_key (str): The private key to sign attestations | ||
| is_production (bool): Whether to use production or testnet | ||
| chain (string): The blockchain network to connect to (e.g., 'Base', 'Arbitrum') | ||
| custom_rpc_url (str): Custom RPC URL to connect to Blockchain | ||
@@ -28,113 +49,313 @@ """ | ||
| # Set network based on environment | ||
| if is_production: | ||
| # Set network (EAS contracts need to be deployed on the target chain) | ||
| if chain.lower() == 'base': | ||
| self.rpc = "https://mainnet.base.org" | ||
| self.graphql = "https://base.easscan.org/graphql" | ||
| self.rpc_chain_number = 8453 | ||
| self.eas_api_url = "https://base.easscan.org/offchain/store" | ||
| self.eas_address = "0x4200000000000000000000000000000000000021" # EAS contract address on mainnet | ||
| else: | ||
| self.rpc = "https://sepolia.base.org" | ||
| self.graphql = "https://base-sepolia.easscan.org/graphql" | ||
| self.rpc_chain_number = 84532 | ||
| self.eas_api_url = "https://base-sepolia.easscan.org/offchain/store" | ||
| self.eas_address = "0x4200000000000000000000000000000000000021" # EAS contract address on testnet | ||
| raise ValueError(f"Unsupported chain: {chain}. Currently only 'Base' is supported.") | ||
| # Use provided RPC endpoint if specified | ||
| # Use provided RPC endpoint if nothing else specified | ||
| if custom_rpc_url is not None: | ||
| self.rpc = custom_rpc_url | ||
| # Initialize Web3 and account | ||
| # Initialize Web3 instance from RPC endpoint | ||
| self.w3 = Web3(Web3.HTTPProvider(self.rpc)) | ||
| if not self.w3.is_connected(): | ||
| raise Exception("Failed to connect to the Ethereum node") | ||
| # Try to get private key from environment if not provided | ||
| # Set local account using private key | ||
| if private_key is None: | ||
| private_key = os.environ.get('OLI_PRIVATE_KEY') | ||
| if not private_key: | ||
| print("WARNING: Private key not provided. Please set the OLI_PRIVATE_KEY environment variable in case you plan to submit labels.") | ||
| print("WARNING: OLI client in read mode only.") | ||
| print("WARNING: Private key not provided. Please set 'private_key' variable when initializing OLI.") | ||
| print("WARNING: OLI client in read mode only.") | ||
| else: | ||
| try: | ||
| # Convert the hex private key to the proper key object | ||
| if private_key.startswith('0x'): | ||
| private_key_bytes = private_key[2:] | ||
| else: | ||
| private_key_bytes = private_key | ||
| # Create account from private key | ||
| private_key_obj = keys.PrivateKey(bytes.fromhex(private_key_bytes)) | ||
| self.account = eth_account.Account.from_key(private_key_obj) | ||
| self.address = self.account.address | ||
| self.source_address = self.address # for Label Trust | ||
| except Exception as e: | ||
| print(f"Error initializing account from private key: {e}") | ||
| self.account = None | ||
| self.address = None | ||
| self.source_address = None # for Label Trust | ||
| self.private_key = private_key | ||
| if self.private_key: | ||
| # Convert the hex private key to the proper key object | ||
| if private_key.startswith('0x'): | ||
| private_key_bytes = private_key[2:] | ||
| else: | ||
| private_key_bytes = private_key | ||
| # Create account from private key | ||
| private_key_obj = keys.PrivateKey(bytes.fromhex(private_key_bytes)) | ||
| self.account = eth_account.Account.from_key(private_key_obj) | ||
| self.address = self.account.address | ||
| # Label Pool Schema for OLI | ||
| # Label Pool Schema for OLI Label Pool | ||
| self.oli_label_pool_schema = '0xb763e62d940bed6f527dd82418e146a904e62a297b8fa765c9b3e1f0bc6fdd68' | ||
| # Load EAS ABI | ||
| # Label Trust Schema for OLI Label Trust | ||
| self.oli_label_trust_schema = '0x6d780a85bfad501090cd82868a0c773c09beafda609d54888a65c106898c363d' | ||
| # Load EAS ABI & contract | ||
| self.eas_abi = '[{"inputs": [],"stateMutability": "nonpayable","type": "constructor"},{"inputs": [],"name": "AccessDenied","type": "error"},{"inputs": [],"name": "AlreadyRevoked","type": "error"},{"inputs": [],"name": "AlreadyRevokedOffchain","type": "error"},{"inputs": [],"name": "AlreadyTimestamped","type": "error"},{"inputs": [],"name": "DeadlineExpired","type": "error"},{"inputs": [],"name": "InsufficientValue","type": "error"},{"inputs": [],"name": "InvalidAttestation","type": "error"},{"inputs": [],"name": "InvalidAttestations","type": "error"},{"inputs": [],"name": "InvalidExpirationTime","type": "error"},{"inputs": [],"name": "InvalidLength","type": "error"},{"inputs": [],"name": "InvalidNonce","type": "error"},{"inputs": [],"name": "InvalidOffset","type": "error"},{"inputs": [],"name": "InvalidRegistry","type": "error"},{"inputs": [],"name": "InvalidRevocation","type": "error"},{"inputs": [],"name": "InvalidRevocations","type": "error"},{"inputs": [],"name": "InvalidSchema","type": "error"},{"inputs": [],"name": "InvalidSignature","type": "error"},{"inputs": [],"name": "InvalidVerifier","type": "error"},{"inputs": [],"name": "Irrevocable","type": "error"},{"inputs": [],"name": "NotFound","type": "error"},{"inputs": [],"name": "NotPayable","type": "error"},{"inputs": [],"name": "WrongSchema","type": "error"},{"anonymous": false,"inputs": [{"indexed": true,"internalType": "address","name": "recipient","type": "address"},{"indexed": true,"internalType": "address","name": "attester","type": "address"},{"indexed": false,"internalType": "bytes32","name": "uid","type": "bytes32"},{"indexed": true,"internalType": "bytes32","name": "schemaUID","type": "bytes32"}],"name": "Attested","type": "event"},{"anonymous": false,"inputs": [{"indexed": false,"internalType": "uint256","name": "oldNonce","type": "uint256"},{"indexed": false,"internalType": "uint256","name": "newNonce","type": "uint256"}],"name": "NonceIncreased","type": "event"},{"anonymous": false,"inputs": [{"indexed": true,"internalType": "address","name": "recipient","type": "address"},{"indexed": true,"internalType": "address","name": "attester","type": "address"},{"indexed": false,"internalType": "bytes32","name": "uid","type": "bytes32"},{"indexed": true,"internalType": "bytes32","name": "schemaUID","type": "bytes32"}],"name": "Revoked","type": "event"},{"anonymous": false,"inputs": [{"indexed": true,"internalType": "address","name": "revoker","type": "address"},{"indexed": true,"internalType": "bytes32","name": "data","type": "bytes32"},{"indexed": true,"internalType": "uint64","name": "timestamp","type": "uint64"}],"name": "RevokedOffchain","type": "event"},{"anonymous": false,"inputs": [{"indexed": true,"internalType": "bytes32","name": "data","type": "bytes32"},{"indexed": true,"internalType": "uint64","name": "timestamp","type": "uint64"}],"name": "Timestamped","type": "event"},{"inputs": [{"components": [{"internalType": "bytes32","name": "schema","type": "bytes32"},{"components": [{"internalType": "address","name": "recipient","type": "address"},{"internalType": "uint64","name": "expirationTime","type": "uint64"},{"internalType": "bool","name": "revocable","type": "bool"},{"internalType": "bytes32","name": "refUID","type": "bytes32"},{"internalType": "bytes","name": "data","type": "bytes"},{"internalType": "uint256","name": "value","type": "uint256"}],"internalType": "struct AttestationRequestData","name": "data","type": "tuple"}],"internalType": "struct AttestationRequest","name": "request","type": "tuple"}],"name": "attest","outputs": [{"internalType": "bytes32","name": "","type": "bytes32"}],"stateMutability": "payable","type": "function"},{"inputs": [{"components": [{"internalType": "bytes32","name": "schema","type": "bytes32"},{"components": [{"internalType": "address","name": "recipient","type": "address"},{"internalType": "uint64","name": "expirationTime","type": "uint64"},{"internalType": "bool","name": "revocable","type": "bool"},{"internalType": "bytes32","name": "refUID","type": "bytes32"},{"internalType": "bytes","name": "data","type": "bytes"},{"internalType": "uint256","name": "value","type": "uint256"}],"internalType": "struct AttestationRequestData","name": "data","type": "tuple"},{"components": [{"internalType": "uint8","name": "v","type": "uint8"},{"internalType": "bytes32","name": "r","type": "bytes32"},{"internalType": "bytes32","name": "s","type": "bytes32"}],"internalType": "struct Signature","name": "signature","type": "tuple"},{"internalType": "address","name": "attester","type": "address"},{"internalType": "uint64","name": "deadline","type": "uint64"}],"internalType": "struct DelegatedAttestationRequest","name": "delegatedRequest","type": "tuple"}],"name": "attestByDelegation","outputs": [{"internalType": "bytes32","name": "","type": "bytes32"}],"stateMutability": "payable","type": "function"},{"inputs": [],"name": "getAttestTypeHash","outputs": [{"internalType": "bytes32","name": "","type": "bytes32"}],"stateMutability": "pure","type": "function"},{"inputs": [{"internalType": "bytes32","name": "uid","type": "bytes32"}],"name": "getAttestation","outputs": [{"components": [{"internalType": "bytes32","name": "uid","type": "bytes32"},{"internalType": "bytes32","name": "schema","type": "bytes32"},{"internalType": "uint64","name": "time","type": "uint64"},{"internalType": "uint64","name": "expirationTime","type": "uint64"},{"internalType": "uint64","name": "revocationTime","type": "uint64"},{"internalType": "bytes32","name": "refUID","type": "bytes32"},{"internalType": "address","name": "recipient","type": "address"},{"internalType": "address","name": "attester","type": "address"},{"internalType": "bool","name": "revocable","type": "bool"},{"internalType": "bytes","name": "data","type": "bytes"}],"internalType": "struct Attestation","name": "","type": "tuple"}],"stateMutability": "view","type": "function"},{"inputs": [],"name": "getDomainSeparator","outputs": [{"internalType": "bytes32","name": "","type": "bytes32"}],"stateMutability": "view","type": "function"},{"inputs": [],"name": "getName","outputs": [{"internalType": "string","name": "","type": "string"}],"stateMutability": "view","type": "function"},{"inputs": [{"internalType": "address","name": "account","type": "address"}],"name": "getNonce","outputs": [{"internalType": "uint256","name": "","type": "uint256"}],"stateMutability": "view","type": "function"},{"inputs": [{"internalType": "address","name": "revoker","type": "address"},{"internalType": "bytes32","name": "data","type": "bytes32"}],"name": "getRevokeOffchain","outputs": [{"internalType": "uint64","name": "","type": "uint64"}],"stateMutability": "view","type": "function"},{"inputs": [],"name": "getRevokeTypeHash","outputs": [{"internalType": "bytes32","name": "","type": "bytes32"}],"stateMutability": "pure","type": "function"},{"inputs": [],"name": "getSchemaRegistry","outputs": [{"internalType": "contract ISchemaRegistry","name": "","type": "address"}],"stateMutability": "pure","type": "function"},{"inputs": [{"internalType": "bytes32","name": "data","type": "bytes32"}],"name": "getTimestamp","outputs": [{"internalType": "uint64","name": "","type": "uint64"}],"stateMutability": "view","type": "function"},{"inputs": [{"internalType": "uint256","name": "newNonce","type": "uint256"}],"name": "increaseNonce","outputs": [],"stateMutability": "nonpayable","type": "function"},{"inputs": [{"internalType": "bytes32","name": "uid","type": "bytes32"}],"name": "isAttestationValid","outputs": [{"internalType": "bool","name": "","type": "bool"}],"stateMutability": "view","type": "function"},{"inputs": [{"components": [{"internalType": "bytes32","name": "schema","type": "bytes32"},{"components": [{"internalType": "address","name": "recipient","type": "address"},{"internalType": "uint64","name": "expirationTime","type": "uint64"},{"internalType": "bool","name": "revocable","type": "bool"},{"internalType": "bytes32","name": "refUID","type": "bytes32"},{"internalType": "bytes","name": "data","type": "bytes"},{"internalType": "uint256","name": "value","type": "uint256"}],"internalType": "struct AttestationRequestData[]","name": "data","type": "tuple[]"}],"internalType": "struct MultiAttestationRequest[]","name": "multiRequests","type": "tuple[]"}],"name": "multiAttest","outputs": [{"internalType": "bytes32[]","name": "","type": "bytes32[]"}],"stateMutability": "payable","type": "function"},{"inputs": [{"components": [{"internalType": "bytes32","name": "schema","type": "bytes32"},{"components": [{"internalType": "address","name": "recipient","type": "address"},{"internalType": "uint64","name": "expirationTime","type": "uint64"},{"internalType": "bool","name": "revocable","type": "bool"},{"internalType": "bytes32","name": "refUID","type": "bytes32"},{"internalType": "bytes","name": "data","type": "bytes"},{"internalType": "uint256","name": "value","type": "uint256"}],"internalType": "struct AttestationRequestData[]","name": "data","type": "tuple[]"},{"components": [{"internalType": "uint8","name": "v","type": "uint8"},{"internalType": "bytes32","name": "r","type": "bytes32"},{"internalType": "bytes32","name": "s","type": "bytes32"}],"internalType": "struct Signature[]","name": "signatures","type": "tuple[]"},{"internalType": "address","name": "attester","type": "address"},{"internalType": "uint64","name": "deadline","type": "uint64"}],"internalType": "struct MultiDelegatedAttestationRequest[]","name": "multiDelegatedRequests","type": "tuple[]"}],"name": "multiAttestByDelegation","outputs": [{"internalType": "bytes32[]","name": "","type": "bytes32[]"}],"stateMutability": "payable","type": "function"},{"inputs": [{"components": [{"internalType": "bytes32","name": "schema","type": "bytes32"},{"components": [{"internalType": "bytes32","name": "uid","type": "bytes32"},{"internalType": "uint256","name": "value","type": "uint256"}],"internalType": "struct RevocationRequestData[]","name": "data","type": "tuple[]"}],"internalType": "struct MultiRevocationRequest[]","name": "multiRequests","type": "tuple[]"}],"name": "multiRevoke","outputs": [],"stateMutability": "payable","type": "function"},{"inputs": [{"components": [{"internalType": "bytes32","name": "schema","type": "bytes32"},{"components": [{"internalType": "bytes32","name": "uid","type": "bytes32"},{"internalType": "uint256","name": "value","type": "uint256"}],"internalType": "struct RevocationRequestData[]","name": "data","type": "tuple[]"},{"components": [{"internalType": "uint8","name": "v","type": "uint8"},{"internalType": "bytes32","name": "r","type": "bytes32"},{"internalType": "bytes32","name": "s","type": "bytes32"}],"internalType": "struct Signature[]","name": "signatures","type": "tuple[]"},{"internalType": "address","name": "revoker","type": "address"},{"internalType": "uint64","name": "deadline","type": "uint64"}],"internalType": "struct MultiDelegatedRevocationRequest[]","name": "multiDelegatedRequests","type": "tuple[]"}],"name": "multiRevokeByDelegation","outputs": [],"stateMutability": "payable","type": "function"},{"inputs": [{"internalType": "bytes32[]","name": "data","type": "bytes32[]"}],"name": "multiRevokeOffchain","outputs": [{"internalType": "uint64","name": "","type": "uint64"}],"stateMutability": "nonpayable","type": "function"},{"inputs": [{"internalType": "bytes32[]","name": "data","type": "bytes32[]"}],"name": "multiTimestamp","outputs": [{"internalType": "uint64","name": "","type": "uint64"}],"stateMutability": "nonpayable","type": "function"},{"inputs": [{"components": [{"internalType": "bytes32","name": "schema","type": "bytes32"},{"components": [{"internalType": "bytes32","name": "uid","type": "bytes32"},{"internalType": "uint256","name": "value","type": "uint256"}],"internalType": "struct RevocationRequestData","name": "data","type": "tuple"}],"internalType": "struct RevocationRequest","name": "request","type": "tuple"}],"name": "revoke","outputs": [],"stateMutability": "payable","type": "function"},{"inputs": [{"components": [{"internalType": "bytes32","name": "schema","type": "bytes32"},{"components": [{"internalType": "bytes32","name": "uid","type": "bytes32"},{"internalType": "uint256","name": "value","type": "uint256"}],"internalType": "struct RevocationRequestData","name": "data","type": "tuple"},{"components": [{"internalType": "uint8","name": "v","type": "uint8"},{"internalType": "bytes32","name": "r","type": "bytes32"},{"internalType": "bytes32","name": "s","type": "bytes32"}],"internalType": "struct Signature","name": "signature","type": "tuple"},{"internalType": "address","name": "revoker","type": "address"},{"internalType": "uint64","name": "deadline","type": "uint64"}],"internalType": "struct DelegatedRevocationRequest","name": "delegatedRequest","type": "tuple"}],"name": "revokeByDelegation","outputs": [],"stateMutability": "payable","type": "function"},{"inputs": [{"internalType": "bytes32","name": "data","type": "bytes32"}],"name": "revokeOffchain","outputs": [{"internalType": "uint64","name": "","type": "uint64"}],"stateMutability": "nonpayable","type": "function"},{"inputs": [{"internalType": "bytes32","name": "data","type": "bytes32"}],"name": "timestamp","outputs": [{"internalType": "uint64","name": "","type": "uint64"}],"stateMutability": "nonpayable","type": "function"},{"inputs": [],"name": "version","outputs": [{"internalType": "string","name": "","type": "string"}],"stateMutability": "view","type": "function"}]' | ||
| self.eas = self.w3.eth.contract(address=self.eas_address, abi=self.eas_abi) | ||
| # Initialize EAS contract | ||
| self.eas = self.w3.eth.contract(address=self.eas_address, abi=self.eas_abi) | ||
| # Initialize components | ||
| self.data_fetcher = DataFetcher(self) | ||
| self.tag_definitions = self.data_fetcher.get_OLI_tags() | ||
| self.tag_ids = list(self.tag_definitions.keys()) | ||
| self.tag_value_sets = self.data_fetcher.get_OLI_value_sets() | ||
| # Initialize validator | ||
| # Initialize OLI Label Schema | ||
| self.api = API(self, api_key=api_key) | ||
| self.tag_definitions = self.api.get_OLI_tags() | ||
| try: | ||
| self.tag_ids = list(self.tag_definitions.keys()) | ||
| self.tag_value_sets = self.api.get_OLI_value_sets() | ||
| except Exception as e: | ||
| self.tag_ids = [] | ||
| self.tag_value_sets = {} | ||
| # Initialize OLI Label Pool | ||
| self.onchain = OnchainAttestations(self, private_key) | ||
| self.offchain = OffchainAttestations(self, private_key) | ||
| self.utils_other = UtilsOther(self) | ||
| self.validator = UtilsValidator(self) | ||
| # Initialize other utilities | ||
| self.utils_other = UtilsOther(self) | ||
| # Initialize onchain and offchain attestations | ||
| self.onchain = OnchainAttestations(self) | ||
| self.offchain = OffchainAttestations(self) | ||
| # Initialize GraphQL client | ||
| self.graphql_client = GraphQLClient(self) | ||
| # Initialize OLI Label Trust | ||
| self.utils_data = UtilsData(self) | ||
| self.trust = UtilsTrust(self) | ||
| print("...OLI client successfully initialized.") | ||
| # OLI Label Pool post functions | ||
| def submit_label(self, address: str, chain_id: str, tags: dict, onchain: bool=False, ref_uid: str="0x0000000000000000000000000000000000000000000000000000000000000000", gas_limit: int=-1, retry: int=4) -> PostResponse: | ||
| try: | ||
| if onchain: | ||
| txn_hash, uid = self.onchain.submit_onchain_label(address, chain_id, tags, ref_uid, gas_limit) | ||
| return PostResponse( | ||
| success=True, | ||
| onchain=True, | ||
| transaction_hash=txn_hash, | ||
| uid=uid, | ||
| eas_schema_chain=self.rpc_chain_number, | ||
| eas_schema=self.oli_label_pool_schema | ||
| ).to_dict() | ||
| else: | ||
| response, uid = self.offchain.submit_offchain_label(address, chain_id, tags, ref_uid, retry) | ||
| data = response.json() | ||
| return PostResponse( | ||
| success=True, | ||
| onchain=False, | ||
| uid=uid if isinstance(uid, str) else uid[0] if uid else None, | ||
| eas_schema=self.oli_label_pool_schema, | ||
| status=data.get('status') | ||
| ).to_dict() | ||
| except Exception as e: | ||
| return PostResponse( | ||
| success=False, | ||
| onchain=onchain, | ||
| error=str(e) | ||
| ).to_dict() | ||
| def submit_label_bulk(self, labels: list, onchain: bool=False, gas_limit: int=-1, retry: int=4) -> PostResponse: | ||
| try: | ||
| if onchain: | ||
| txn_hash, uids = self.onchain.submit_multi_onchain_labels(labels, gas_limit) | ||
| return PostResponse( | ||
| success=True, | ||
| onchain=True, | ||
| transaction_hash=txn_hash, | ||
| uids=uids if isinstance(uids, list) else [uids], | ||
| eas_schema_chain=self.rpc_chain_number, | ||
| eas_schema=self.oli_label_pool_schema | ||
| ).to_dict() | ||
| else: | ||
| response, uids = self.offchain.submit_offchain_labels_bulk(labels, retry) | ||
| data = response.json() | ||
| return PostResponse( | ||
| success=True, | ||
| onchain=False, | ||
| uids=uids if uids else None, | ||
| eas_schema=self.oli_label_pool_schema, | ||
| status=data.get('status'), | ||
| accepted=data.get('accepted'), | ||
| duplicates=data.get('duplicates'), | ||
| failed_validation=data.get('failed_validation') | ||
| ).to_dict() | ||
| except Exception as e: | ||
| return PostResponse( | ||
| success=False, | ||
| onchain=onchain, | ||
| error=str(e) | ||
| ).to_dict() | ||
| def submit_trust_list(self, owner_name: str, attesters: list=[], attestations: list=[], onchain: bool=False, gas_limit: int=-1, retry: int=4) -> PostResponse: | ||
| try: | ||
| if onchain: | ||
| txn_hash, uid = self.onchain.submit_onchain_trust_list(owner_name, attesters, attestations, gas_limit) | ||
| return PostResponse( | ||
| success=True, | ||
| onchain=True, | ||
| transaction_hash=txn_hash, | ||
| uid=uid, | ||
| eas_schema_chain=self.rpc_chain_number, | ||
| eas_schema=self.oli_label_trust_schema | ||
| ).to_dict() | ||
| else: | ||
| response, uid = self.offchain.submit_offchain_trust_list(owner_name, attesters, attestations, retry) | ||
| data = response.json() | ||
| return PostResponse( | ||
| success=True, | ||
| onchain=False, | ||
| uid=uid if isinstance(uid, str) else uid[0] if uid else None, | ||
| eas_schema=self.oli_label_trust_schema, | ||
| status=data.get('status') | ||
| ).to_dict() | ||
| except Exception as e: | ||
| return PostResponse( | ||
| success=False, | ||
| onchain=onchain, | ||
| error=str(e) | ||
| ).to_dict() | ||
| # Expose onchain attestation methods | ||
| def submit_onchain_label(self, address: str, chain_id: str, tags: dict, ref_uid: str="0x0000000000000000000000000000000000000000000000000000000000000000", gas_limit: int=0) -> tuple[str, str]: | ||
| return self.onchain.submit_onchain_label(address, chain_id, tags, ref_uid, gas_limit) | ||
| def submit_multi_onchain_labels(self, labels: list, gas_limit: int=0) -> tuple[str, list]: | ||
| return self.onchain.submit_multi_onchain_labels(labels, gas_limit) | ||
| # Expose offchain attestation methods | ||
| def submit_offchain_label(self, address: str, chain_id: str, tags: dict, ref_uid: str="0x0000000000000000000000000000000000000000000000000000000000000000", retry: int=4) -> Response: | ||
| return self.offchain.submit_offchain_label(address, chain_id, tags, ref_uid, retry) | ||
| # Expose revocation methods | ||
| def revoke_attestation(self, uid_hex: str, onchain: bool, gas_limit: int=200000) -> str: | ||
| if onchain: | ||
| return self.onchain.revoke_attestation(uid_hex, gas_limit) | ||
| else: | ||
| return self.offchain.revoke_attestation(uid_hex, gas_limit) | ||
| def multi_revoke_attestations(self, uids: str, onchain: bool, gas_limit: int=10000000) -> str: | ||
| if onchain: | ||
| return self.onchain.multi_revoke_attestations(uids, gas_limit) | ||
| else: | ||
| return self.offchain.multi_revoke_attestations(uids, gas_limit) | ||
| # Expose query methods | ||
| def graphql_query_attestations(self, address: str=None, attester: str=None, timeCreated: int=None, revocationTime: int=None, take: int=None, id: str=None,expand_json: bool=True) -> dict: | ||
| return self.graphql_client.graphql_query_attestations(address, attester, timeCreated, revocationTime, take, id, expand_json) | ||
| def revoke_by_uid(self, uid: str, onchain: bool=False, gas_limit: int=200000) -> dict: | ||
| # please note onchain revocations require ETH for gas fees | ||
| try: | ||
| # revoke based on onchain parameter | ||
| if onchain: | ||
| txn_hash = self.onchain.revoke_attestation(uid, gas_limit) | ||
| else: | ||
| txn_hash = self.offchain.revoke_attestation(uid, gas_limit) | ||
| return PostResponse( | ||
| success=True, | ||
| onchain=True, | ||
| transaction_hash=txn_hash, | ||
| uid=uid, | ||
| eas_schema_chain=self.rpc_chain_number | ||
| ).to_dict() | ||
| except Exception as e: | ||
| return PostResponse( | ||
| success=False, | ||
| onchain=True, | ||
| uid=uid, | ||
| eas_schema_chain=self.rpc_chain_number, | ||
| error=str(e) | ||
| ).to_dict() | ||
| def revoke_bulk_by_uids(self, uids: list, onchain: bool=False, gas_limit: int=10000000) -> dict: | ||
| # please note onchain revocations require ETH for gas fees, furthermore it is recommended to keep the max number of uids to 100 | ||
| try: | ||
| # revoke based on onchain parameter | ||
| if onchain: | ||
| txn_hash = self.onchain.multi_revoke_attestations(uids, gas_limit) | ||
| else: | ||
| txn_hash = self.offchain.multi_revoke_attestations(uids, gas_limit) | ||
| return PostResponse( | ||
| success=True, | ||
| onchain=True, | ||
| transaction_hash=txn_hash, | ||
| uids=uids, | ||
| eas_schema_chain=self.rpc_chain_number | ||
| ).to_dict() | ||
| except Exception as e: | ||
| return PostResponse( | ||
| success=False, | ||
| onchain=True, | ||
| uids=uids, | ||
| eas_schema_chain=self.rpc_chain_number, | ||
| error=str(e) | ||
| ).to_dict() | ||
| # OLI Label Pool validation functions | ||
| def validate_label(self, address: str, chain_id: str, tags: dict, ref_uid: str="0x0000000000000000000000000000000000000000000000000000000000000000", auto_fix: bool=True) -> bool: | ||
| return self.validator.validate_label_correctness(address, chain_id, tags, ref_uid, auto_fix) | ||
| def validate_trust_list(self, owner_name: str, attesters: list=[], attestations: list=[]) -> bool: | ||
| return self.validator.validate_trust_list_correctness(owner_name, attesters, attestations) | ||
| # OLI Label Pool parquet exports | ||
| def get_full_raw_export_parquet(self, file_path: str="raw_labels.parquet") -> str: | ||
| return self.data_fetcher.get_full_raw_export_parquet(file_path) | ||
| return self.api.get_full_raw_export_parquet(file_path) | ||
| def get_full_decoded_export_parquet(self, file_path: str="decoded_labels.parquet") -> str: | ||
| return self.data_fetcher.get_full_decoded_export_parquet(file_path) | ||
| return self.api.get_full_decoded_export_parquet(file_path) | ||
| # OLI Label Trust functions | ||
| def set_trust_node(self, address: str) -> None: | ||
| if self.validator.validate_address(address): | ||
| # set the source address for trust calculations | ||
| self.source_address = address | ||
| # compute the trust table for the new source address | ||
| self.trust.trust_table = self.trust.compute_trust_table(self.trust.TrustGraph, source_node=address) | ||
| return self.trust.trust_table | ||
| def add_trust_list(self, owner_name: str='private_node', attesters: list=[], attestations: list=[], attester_address: str='0x0000000000000000000000000000000000000000') -> None: | ||
| # add the trust list to the trust graph | ||
| success = self.trust.add_trust_list(owner_name, attesters, attestations, attester_address) | ||
| if success: | ||
| # recompute the trust table for the current saved 'source_address' | ||
| self.trust.trust_table = self.trust.compute_trust_table(self.trust.TrustGraph, source_node=self.source_address) | ||
| return True | ||
| # OLI Label Pool API functions (no api key required) | ||
| def get_attestations(self, uid: str = None, attester: str = None, recipient: str = None, schema_info: str = None, since: str = None, order: str = "desc", limit: int = 1000) -> dict: | ||
| return self.api.get_attestations(uid, attester, recipient, schema_info, since, order, limit) | ||
| def get_trust_lists(self, uid: str = None, attester: str = None, order: str = "desc", limit: int = 100) -> dict: | ||
| return self.api.get_trust_lists(uid, attester, order, limit) | ||
| # OLI Label Pool API functions (api key required) | ||
| def get_labels(self, address: str, chain_id: str=None, limit: int=1000, include_all: bool=False) -> dict: | ||
| return self.api.get_labels(address, chain_id, limit, include_all) | ||
| # Expose validation methods | ||
| def validate_label_correctness(self, address: str, chain_id: str, tags: dict, ref_uid: str="0x0000000000000000000000000000000000000000000000000000000000000000", auto_fix: bool=True) -> bool: | ||
| return self.validator.validate_label_correctness(address, chain_id, tags, ref_uid, auto_fix) | ||
| def get_trusted_labels(self, address: str, chain_id: str=None, limit: int=1000, include_all: bool=False, min_confidence: float=-1) -> dict: | ||
| response = self.api.get_labels(address, chain_id, limit, include_all) | ||
| filtered_labels = [] | ||
| for label in response['labels']: | ||
| label['confidence'] = self.utils_data.get_confidence(label['attester'], label['tag_id'], label['chain_id']) | ||
| if label['confidence'] >= min_confidence or min_confidence == -1: | ||
| filtered_labels.append(label) | ||
| response['labels'] = filtered_labels | ||
| response['count_trusted'] = len(filtered_labels) | ||
| return response | ||
| def fix_simple_tags_formatting(self, tags: dict) -> dict: | ||
| return self.validator.fix_simple_tags_formatting(tags) | ||
| def get_labels_bulk(self, addresses: list, chain_id: str = None, limit_per_address: int = 1000, include_all: bool = False) -> dict: | ||
| return self.api.get_labels_bulk(addresses, chain_id, limit_per_address, include_all) | ||
| def get_trusted_labels_bulk(self, addresses: list, chain_id: str = None, limit_per_address: int = 1000, include_all: bool = False, min_confidence: float = -1) -> dict: | ||
| response = self.api.get_labels_bulk(addresses, chain_id, limit_per_address, include_all) | ||
| for i, address in enumerate(response['results']): | ||
| filtered_labels = [] | ||
| labels = address['labels'] | ||
| for label in labels: | ||
| label['confidence'] = self.utils_data.get_confidence(label['attester'], label['tag_id'], label['chain_id']) | ||
| if label['confidence'] >= min_confidence or min_confidence == -1: | ||
| filtered_labels.append(label) | ||
| response['results'][i] = {'address': address['address'], 'labels': filtered_labels} | ||
| response['results'][i]['count'] = len(labels) | ||
| response['results'][i]['count_trusted'] = len(filtered_labels) | ||
| return response | ||
| def search_addresses_by_tag(self, tag_id: str, tag_value: str, chain_id: str = None, limit: int = 1000) -> dict: | ||
| return self.api.search_addresses_by_tag(tag_id, tag_value, chain_id, limit) | ||
| def search_trusted_addresses_by_tag(self, tag_id: str, tag_value: str, chain_id: str = None, limit: int = 1000, min_confidence: float = -1) -> dict: | ||
| response = self.api.search_addresses_by_tag(tag_id, tag_value, chain_id, limit) | ||
| filtered_addresses = [] | ||
| for label in response['results']: | ||
| label['confidence'] = self.utils_data.get_confidence(label['attester'], tag_id, label['chain_id']) | ||
| if label['confidence'] >= min_confidence or min_confidence == -1: | ||
| filtered_addresses.append(label) | ||
| response['results'] = filtered_addresses | ||
| response['count_trusted'] = len(filtered_addresses) | ||
| return response | ||
| def get_attester_analytics(self, chain_id: str = None, limit: int = 100) -> dict: | ||
| return self.api.get_attester_analytics(chain_id, limit) |
@@ -1,4 +0,5 @@ | ||
| from oli.data.graphql import GraphQLClient | ||
| from oli.data.fetcher import DataFetcher | ||
| from oli.data.api import API | ||
| from oli.data.trust import UtilsTrust | ||
| from oli.data.utils import UtilsData | ||
| __all__ = ["GraphQLClient", "DataFetcher"] | ||
| __all__ = ["API", "UtilsTrust", "UtilsData"] |
+179
-56
| Metadata-Version: 2.4 | ||
| Name: oli-python | ||
| Version: 1.2.1 | ||
| Version: 2.0.0 | ||
| Summary: Python SDK for interacting with the Open Labels Initiative; A standard, registry and trust layer for EVM address labels. | ||
| Home-page: https://github.com/openlabelsinitiative/oli-python | ||
| Author: Lorenz Lehmann | ||
| Author-email: lorenz@growthepie.xyz | ||
| Author-email: lorenz@growthepie.com | ||
| Classifier: Programming Language :: Python :: 3 | ||
@@ -28,6 +28,8 @@ Classifier: License :: OSI Approved :: MIT License | ||
| # OLI Python Package | ||
| # OLI Python SDK | ||
| Python SDK for interacting with the Open Labels Initiative; A framework for address labels in the blockchain space. Read & write labels into the OLI Label Pool, check your labels for OLI compliance. | ||
| **`oli-python`** is the official Python SDK for the **[Open Labels Initiative (OLI)](https://github.com/openlabelsinitiative/OLI)**, a framework for standardized and trusted blockchain address labels. | ||
| This SDK lets you read, write, validate, and revoke address labels within the **OLI Label Pool** and compute confidence scores based on attesters' relationships within the **OLI Label Trust**. | ||
| ## Installation | ||
@@ -39,3 +41,3 @@ | ||
| ### Submitting New Labels | ||
| ## Initialization | ||
@@ -47,87 +49,208 @@ ```python | ||
| # Initialize the client | ||
| # Make sure to pull in your private key from an .env file | ||
| oli = OLI(private_key=os.environ['OLI_PRIVATE_KEY'], is_production=True) | ||
| oli = OLI( | ||
| private_key=os.getenv("PRIVATE_KEY"), | ||
| api_key=os.getenv("OLI_API_KEY") | ||
| ) | ||
| ``` | ||
| # Create a label | ||
| **Setup Instructions:** | ||
| * Create a `.env` file to store your private variables | ||
| * Load an EOA wallet by setting the `private_key` variable | ||
| * The EOA wallet is used to track your reputation within OLI | ||
| * To access all advanced API endpoints, pass an `api_key` variable. You can get your free API key from **[here](http://openlabelsinitiative.org/developer)** | ||
| **Additional Options:** | ||
| You can also set the `chain` parameter to specify which EAS contracts location to use and the `custom_rpc_url` variable when initializing. | ||
| ## Submit Labels into the OLI Label Pool | ||
| ### Single Attestation | ||
| ```python | ||
| # Define your OLI compliant attestation | ||
| address = "0x9438b8B447179740cD97869997a2FCc9b4AA63a2" | ||
| chain_id = "eip155:1" # Ethereum Mainnet | ||
| chain_id = "eip155:1" # Ethereum mainnet | ||
| tags = { | ||
| "contract_name": "growthepie donation address", | ||
| "is_eoa": True, | ||
| "owner_project": "growthepie" | ||
| "owner_project": "growthepie", | ||
| } | ||
| # Validate if your label is OLI compliant | ||
| possible_to_attest = oli.validate_label_correctness(address, chain_id, tags) | ||
| print(f"You can attest your label: {possible_to_attest}") | ||
| # (Optional) Validate your attestation before submission | ||
| is_valid = oli.validate_label(address, chain_id, tags) | ||
| print("Your attestation is OLI compliant:", is_valid) | ||
| # Submit a label as an offchain attestation | ||
| response = oli.submit_offchain_label(address, chain_id, tags) | ||
| print(f"Label submitted offchain with response: {response.text}") | ||
| # Submit label to the OLI Label Pool | ||
| response = oli.submit_label(address, chain_id, tags) | ||
| print(response) | ||
| ``` | ||
| # Submit a label as an onchain attestation | ||
| tx_hash, uid = oli.submit_onchain_label(address, chain_id, tags) | ||
| print(f"Label submitted onchain with hash: {tx_hash} and uid: {uid}") | ||
| ### Bulk Attestation | ||
| # Batch submit multiple labels as one onchain attestation | ||
| labels = [ | ||
| {'address': address, 'chain_id': chain_id, 'tags': tags}, | ||
| {'address': address, 'chain_id': chain_id, 'tags': tags} | ||
| ```python | ||
| attestations = [ | ||
| {"address": address, "chain_id": chain_id, "tags": tags}, | ||
| {"address": address, "chain_id": chain_id, "tags": tags}, | ||
| ] | ||
| tx_hash, uids = oli.submit_multi_onchain_labels(labels) | ||
| print(f"Labels batch submitted onchain with transaction hash: {tx_hash} and uids: {uids}") | ||
| # Revoke an attestation (revoking onchain attestations here) | ||
| trx_hash = oli.revoke_attestation(uid, onchain=True) | ||
| print(f"Label {uid} revoked with hash: {trx_hash}") | ||
| response = oli.submit_label_bulk(attestations) | ||
| print(response) | ||
| ``` | ||
| # Revoke multiple attestations (revoking onchain attestations here) | ||
| trx_hash, count = oli.multi_revoke_attestations(uids, onchain=True) | ||
| print(f"Labels batch revoked with hash: {trx_hash}") | ||
| ## Revoke Attestations | ||
| **Note:** All revocations are onchain transactions and therefore require you to have ETH on Base for gas in your wallet. | ||
| ```python | ||
| # Revoke a single attestation | ||
| oli.revoke_by_uid(uid="0x...") | ||
| # Bulk revocation | ||
| uids = ["0x1...", "0x2..."] | ||
| oli.revoke_bulk_by_uids(uids) | ||
| ``` | ||
| ### Querying Existing Labels | ||
| ## Reading the OLI Label Pool | ||
| ### Get Raw Attestations | ||
| ```python | ||
| from oli import OLI | ||
| import os | ||
| response = oli.get_attestations() | ||
| print(response) | ||
| ``` | ||
| # Initialize the client (read mode only doesn't require a private key) | ||
| oli = OLI() | ||
| **Available filters:** uid, attester, recipient, schema_info, since, order, limit | ||
| # Query attestations for a specific address | ||
| result = oli.graphql_query_attestations(address=address) | ||
| print(result) | ||
| ### Get Labels for an Address | ||
| # Download parquet export of raw attestations | ||
| oli.get_full_raw_export_parquet() | ||
| ```python | ||
| # All labels for an address | ||
| response = oli.get_labels(address = "0x9438b8B447179740cD97869997a2FCc9b4AA63a2") | ||
| print(response) | ||
| ``` | ||
| # Download parquet export of decoded attestations | ||
| oli.get_full_decoded_export_parquet() | ||
| **Available filters:** address, chain_id, limit, include_all | ||
| ### Bulk Get Labels for Addresses | ||
| ```python | ||
| response = ["0x9438b8B447179740cD97869997a2FCc9b4AA63a2", "0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24"] | ||
| bulk = oli.get_labels_bulk(response) | ||
| print(bulk) | ||
| ``` | ||
| **Available filters:** addresses, chain_id, limit_per_address, include_all | ||
| ## Wallet Requirements | ||
| ### Get Addresses Based on a Tag | ||
| The [OLI Label Pool](https://github.com/openlabelsinitiative/OLI/tree/main/2_label_pool) lives on Base as an [Ethereum Attestation schema](https://base.easscan.org/schema/view/0xb763e62d940bed6f527dd82418e146a904e62a297b8fa765c9b3e1f0bc6fdd68). | ||
| ```python | ||
| response = oli.search_addresses_by_tag( | ||
| tag_id="owner_project", | ||
| tag_value="growthepie" | ||
| ) | ||
| print(response) | ||
| ``` | ||
| Make sure your wallet contains ETH on **Base Mainnet** to pay for onchain transaction (including offchain revocations). Offchain attestations are free. | ||
| **Available filters:** tag_id, tag_value, chain_id, limit | ||
| For testing purposes, you can use Base Sepolia Testnet by setting `is_production=False` when initializing the client. | ||
| ### Attester Leaderboard | ||
| ## Features | ||
| ```python | ||
| chain_id = "eip155:1" | ||
| stats = oli.get_attester_analytics(chain_id) | ||
| print(stats) | ||
| ``` | ||
| - Submit onchain (single or batch) and offchain (single) labels into the OLI Label Pool | ||
| - Revoke your own labels (single or batch) | ||
| - Validate if your label is OLI compliant | ||
| - Query attestations using GraphQL | ||
| - Download full dataset exports in Parquet format | ||
| ## OLI Label Trust | ||
| ## Documentation | ||
| ```python | ||
| # Set a new source node for trust propagation | ||
| trust_table = oli.set_trust_node("0xYourAddress") | ||
| print(oli.trust.trust_table) | ||
| For more details, see the [OLI Documentation](https://github.com/openlabelsinitiative/OLI). | ||
| # Import your trust list from YAML file | ||
| import yaml | ||
| with open('example_trust_list.yml', 'r') as file: | ||
| trust_list = yaml.safe_load(file) | ||
| owner_name = trust_list.get('owner_name') | ||
| attesters = trust_list.get('attesters', []) | ||
| attestations = trust_list.get('attestations', []) | ||
| # Check if the trust list is OLI compliant | ||
| is_valid = oli.validate_trust_list( | ||
| owner_name, | ||
| attesters, | ||
| attestations | ||
| ) | ||
| print("Your trust list is OLI compliant:", is_valid) | ||
| # Add a private trust list to the trust graph | ||
| oli.add_trust_list( | ||
| owner_name, | ||
| attesters, | ||
| attestations | ||
| ) | ||
| # Submit your trust list to the global trust graph | ||
| response = oli.submit_trust_list( | ||
| owner_name, | ||
| attesters, | ||
| attestations | ||
| ) | ||
| print(response) | ||
| ``` | ||
| Once you set your trust node or submit your trust list, the transitive trust algorithm can assign a trust score to all labels. Use the following endpoints, which now assign a confidence score to each label. You can also set `min_confidence` to filter based on confidence thresholds. | ||
| ### Get Trusted Labels for an Address | ||
| ```python | ||
| # All trusted labels for an address | ||
| response = oli.get_trusted_labels(address = "0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24") | ||
| print(response) | ||
| ``` | ||
| **Available filters:** address, chain_id, limit, include_all, min_confidence | ||
| ### Bulk Get Trusted Labels for Addresses | ||
| ```python | ||
| addresses = ["0x9438b8B447179740cD97869997a2FCc9b4AA63a2", "0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24"] | ||
| response = oli.get_trusted_labels_bulk(addresses) | ||
| print(response) | ||
| ``` | ||
| **Available filters:** addresses, chain_id, limit_per_address, include_all, min_confidence | ||
| ### Get Trusted Addresses Based on a Tag | ||
| ```python | ||
| response = oli.search_trusted_addresses_by_tag( | ||
| tag_id = "owner_project", | ||
| tag_value = "growthepie" | ||
| ) | ||
| print(response) | ||
| ``` | ||
| **Available filters:** tag_id, tag_value, chain_id, limit, min_confidence | ||
| ## OLI Label Pool Parquet Exports | ||
| growthepie provides dataset exports as Parquet files for all attestations inside the OLI Label Pool, updated daily: | ||
| ```python | ||
| oli.get_full_raw_export_parquet() | ||
| oli.get_full_decoded_export_parquet() | ||
| ``` | ||
| **Option:** Pass the filepath as a variable to store the export in a specific location. | ||
| ## OLI Documentation | ||
| * [Open Labels Initiative Docs](https://github.com/openlabelsinitiative/OLI) | ||
| * [OLI Website](https://www.openlabelsinitiative.org/) | ||
| ## License | ||
| MIT | ||
| MIT © Open Labels Initiative |
+177
-54
@@ -1,5 +0,7 @@ | ||
| # OLI Python Package | ||
| # OLI Python SDK | ||
| Python SDK for interacting with the Open Labels Initiative; A framework for address labels in the blockchain space. Read & write labels into the OLI Label Pool, check your labels for OLI compliance. | ||
| **`oli-python`** is the official Python SDK for the **[Open Labels Initiative (OLI)](https://github.com/openlabelsinitiative/OLI)**, a framework for standardized and trusted blockchain address labels. | ||
| This SDK lets you read, write, validate, and revoke address labels within the **OLI Label Pool** and compute confidence scores based on attesters' relationships within the **OLI Label Trust**. | ||
| ## Installation | ||
@@ -11,3 +13,3 @@ | ||
| ### Submitting New Labels | ||
| ## Initialization | ||
@@ -19,87 +21,208 @@ ```python | ||
| # Initialize the client | ||
| # Make sure to pull in your private key from an .env file | ||
| oli = OLI(private_key=os.environ['OLI_PRIVATE_KEY'], is_production=True) | ||
| oli = OLI( | ||
| private_key=os.getenv("PRIVATE_KEY"), | ||
| api_key=os.getenv("OLI_API_KEY") | ||
| ) | ||
| ``` | ||
| # Create a label | ||
| **Setup Instructions:** | ||
| * Create a `.env` file to store your private variables | ||
| * Load an EOA wallet by setting the `private_key` variable | ||
| * The EOA wallet is used to track your reputation within OLI | ||
| * To access all advanced API endpoints, pass an `api_key` variable. You can get your free API key from **[here](http://openlabelsinitiative.org/developer)** | ||
| **Additional Options:** | ||
| You can also set the `chain` parameter to specify which EAS contracts location to use and the `custom_rpc_url` variable when initializing. | ||
| ## Submit Labels into the OLI Label Pool | ||
| ### Single Attestation | ||
| ```python | ||
| # Define your OLI compliant attestation | ||
| address = "0x9438b8B447179740cD97869997a2FCc9b4AA63a2" | ||
| chain_id = "eip155:1" # Ethereum Mainnet | ||
| chain_id = "eip155:1" # Ethereum mainnet | ||
| tags = { | ||
| "contract_name": "growthepie donation address", | ||
| "is_eoa": True, | ||
| "owner_project": "growthepie" | ||
| "owner_project": "growthepie", | ||
| } | ||
| # Validate if your label is OLI compliant | ||
| possible_to_attest = oli.validate_label_correctness(address, chain_id, tags) | ||
| print(f"You can attest your label: {possible_to_attest}") | ||
| # (Optional) Validate your attestation before submission | ||
| is_valid = oli.validate_label(address, chain_id, tags) | ||
| print("Your attestation is OLI compliant:", is_valid) | ||
| # Submit a label as an offchain attestation | ||
| response = oli.submit_offchain_label(address, chain_id, tags) | ||
| print(f"Label submitted offchain with response: {response.text}") | ||
| # Submit label to the OLI Label Pool | ||
| response = oli.submit_label(address, chain_id, tags) | ||
| print(response) | ||
| ``` | ||
| # Submit a label as an onchain attestation | ||
| tx_hash, uid = oli.submit_onchain_label(address, chain_id, tags) | ||
| print(f"Label submitted onchain with hash: {tx_hash} and uid: {uid}") | ||
| ### Bulk Attestation | ||
| # Batch submit multiple labels as one onchain attestation | ||
| labels = [ | ||
| {'address': address, 'chain_id': chain_id, 'tags': tags}, | ||
| {'address': address, 'chain_id': chain_id, 'tags': tags} | ||
| ```python | ||
| attestations = [ | ||
| {"address": address, "chain_id": chain_id, "tags": tags}, | ||
| {"address": address, "chain_id": chain_id, "tags": tags}, | ||
| ] | ||
| tx_hash, uids = oli.submit_multi_onchain_labels(labels) | ||
| print(f"Labels batch submitted onchain with transaction hash: {tx_hash} and uids: {uids}") | ||
| # Revoke an attestation (revoking onchain attestations here) | ||
| trx_hash = oli.revoke_attestation(uid, onchain=True) | ||
| print(f"Label {uid} revoked with hash: {trx_hash}") | ||
| response = oli.submit_label_bulk(attestations) | ||
| print(response) | ||
| ``` | ||
| # Revoke multiple attestations (revoking onchain attestations here) | ||
| trx_hash, count = oli.multi_revoke_attestations(uids, onchain=True) | ||
| print(f"Labels batch revoked with hash: {trx_hash}") | ||
| ## Revoke Attestations | ||
| **Note:** All revocations are onchain transactions and therefore require you to have ETH on Base for gas in your wallet. | ||
| ```python | ||
| # Revoke a single attestation | ||
| oli.revoke_by_uid(uid="0x...") | ||
| # Bulk revocation | ||
| uids = ["0x1...", "0x2..."] | ||
| oli.revoke_bulk_by_uids(uids) | ||
| ``` | ||
| ### Querying Existing Labels | ||
| ## Reading the OLI Label Pool | ||
| ### Get Raw Attestations | ||
| ```python | ||
| from oli import OLI | ||
| import os | ||
| response = oli.get_attestations() | ||
| print(response) | ||
| ``` | ||
| # Initialize the client (read mode only doesn't require a private key) | ||
| oli = OLI() | ||
| **Available filters:** uid, attester, recipient, schema_info, since, order, limit | ||
| # Query attestations for a specific address | ||
| result = oli.graphql_query_attestations(address=address) | ||
| print(result) | ||
| ### Get Labels for an Address | ||
| # Download parquet export of raw attestations | ||
| oli.get_full_raw_export_parquet() | ||
| ```python | ||
| # All labels for an address | ||
| response = oli.get_labels(address = "0x9438b8B447179740cD97869997a2FCc9b4AA63a2") | ||
| print(response) | ||
| ``` | ||
| # Download parquet export of decoded attestations | ||
| oli.get_full_decoded_export_parquet() | ||
| **Available filters:** address, chain_id, limit, include_all | ||
| ### Bulk Get Labels for Addresses | ||
| ```python | ||
| response = ["0x9438b8B447179740cD97869997a2FCc9b4AA63a2", "0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24"] | ||
| bulk = oli.get_labels_bulk(response) | ||
| print(bulk) | ||
| ``` | ||
| **Available filters:** addresses, chain_id, limit_per_address, include_all | ||
| ## Wallet Requirements | ||
| ### Get Addresses Based on a Tag | ||
| The [OLI Label Pool](https://github.com/openlabelsinitiative/OLI/tree/main/2_label_pool) lives on Base as an [Ethereum Attestation schema](https://base.easscan.org/schema/view/0xb763e62d940bed6f527dd82418e146a904e62a297b8fa765c9b3e1f0bc6fdd68). | ||
| ```python | ||
| response = oli.search_addresses_by_tag( | ||
| tag_id="owner_project", | ||
| tag_value="growthepie" | ||
| ) | ||
| print(response) | ||
| ``` | ||
| Make sure your wallet contains ETH on **Base Mainnet** to pay for onchain transaction (including offchain revocations). Offchain attestations are free. | ||
| **Available filters:** tag_id, tag_value, chain_id, limit | ||
| For testing purposes, you can use Base Sepolia Testnet by setting `is_production=False` when initializing the client. | ||
| ### Attester Leaderboard | ||
| ## Features | ||
| ```python | ||
| chain_id = "eip155:1" | ||
| stats = oli.get_attester_analytics(chain_id) | ||
| print(stats) | ||
| ``` | ||
| - Submit onchain (single or batch) and offchain (single) labels into the OLI Label Pool | ||
| - Revoke your own labels (single or batch) | ||
| - Validate if your label is OLI compliant | ||
| - Query attestations using GraphQL | ||
| - Download full dataset exports in Parquet format | ||
| ## OLI Label Trust | ||
| ## Documentation | ||
| ```python | ||
| # Set a new source node for trust propagation | ||
| trust_table = oli.set_trust_node("0xYourAddress") | ||
| print(oli.trust.trust_table) | ||
| For more details, see the [OLI Documentation](https://github.com/openlabelsinitiative/OLI). | ||
| # Import your trust list from YAML file | ||
| import yaml | ||
| with open('example_trust_list.yml', 'r') as file: | ||
| trust_list = yaml.safe_load(file) | ||
| owner_name = trust_list.get('owner_name') | ||
| attesters = trust_list.get('attesters', []) | ||
| attestations = trust_list.get('attestations', []) | ||
| # Check if the trust list is OLI compliant | ||
| is_valid = oli.validate_trust_list( | ||
| owner_name, | ||
| attesters, | ||
| attestations | ||
| ) | ||
| print("Your trust list is OLI compliant:", is_valid) | ||
| # Add a private trust list to the trust graph | ||
| oli.add_trust_list( | ||
| owner_name, | ||
| attesters, | ||
| attestations | ||
| ) | ||
| # Submit your trust list to the global trust graph | ||
| response = oli.submit_trust_list( | ||
| owner_name, | ||
| attesters, | ||
| attestations | ||
| ) | ||
| print(response) | ||
| ``` | ||
| Once you set your trust node or submit your trust list, the transitive trust algorithm can assign a trust score to all labels. Use the following endpoints, which now assign a confidence score to each label. You can also set `min_confidence` to filter based on confidence thresholds. | ||
| ### Get Trusted Labels for an Address | ||
| ```python | ||
| # All trusted labels for an address | ||
| response = oli.get_trusted_labels(address = "0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24") | ||
| print(response) | ||
| ``` | ||
| **Available filters:** address, chain_id, limit, include_all, min_confidence | ||
| ### Bulk Get Trusted Labels for Addresses | ||
| ```python | ||
| addresses = ["0x9438b8B447179740cD97869997a2FCc9b4AA63a2", "0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24"] | ||
| response = oli.get_trusted_labels_bulk(addresses) | ||
| print(response) | ||
| ``` | ||
| **Available filters:** addresses, chain_id, limit_per_address, include_all, min_confidence | ||
| ### Get Trusted Addresses Based on a Tag | ||
| ```python | ||
| response = oli.search_trusted_addresses_by_tag( | ||
| tag_id = "owner_project", | ||
| tag_value = "growthepie" | ||
| ) | ||
| print(response) | ||
| ``` | ||
| **Available filters:** tag_id, tag_value, chain_id, limit, min_confidence | ||
| ## OLI Label Pool Parquet Exports | ||
| growthepie provides dataset exports as Parquet files for all attestations inside the OLI Label Pool, updated daily: | ||
| ```python | ||
| oli.get_full_raw_export_parquet() | ||
| oli.get_full_decoded_export_parquet() | ||
| ``` | ||
| **Option:** Pass the filepath as a variable to store the export in a specific location. | ||
| ## OLI Documentation | ||
| * [Open Labels Initiative Docs](https://github.com/openlabelsinitiative/OLI) | ||
| * [OLI Website](https://www.openlabelsinitiative.org/) | ||
| ## License | ||
| MIT | ||
| MIT © Open Labels Initiative |
+2
-2
@@ -8,5 +8,5 @@ from setuptools import setup, find_packages | ||
| name="oli-python", | ||
| version="1.2.1", | ||
| version="2.0.0", | ||
| author="Lorenz Lehmann", | ||
| author_email="lorenz@growthepie.xyz", | ||
| author_email="lorenz@growthepie.com", | ||
| description="Python SDK for interacting with the Open Labels Initiative; A standard, registry and trust layer for EVM address labels.", | ||
@@ -13,0 +13,0 @@ long_description=long_description, |
| import requests | ||
| import yaml | ||
| class DataFetcher: | ||
| def __init__(self, oli_client): | ||
| """ | ||
| Initialize the DataFetcher with an OLI client. | ||
| Args: | ||
| oli_client: The OLI client instance | ||
| """ | ||
| self.oli = oli_client | ||
| def get_OLI_tags(self): | ||
| """ | ||
| Get latest OLI tags from OLI Github repo. | ||
| Returns: | ||
| dict: Dictionary of official OLI tags | ||
| """ | ||
| url = "https://raw.githubusercontent.com/openlabelsinitiative/OLI/refs/heads/main/1_label_schema/tags/tag_definitions.yml" | ||
| response = requests.get(url) | ||
| if response.status_code == 200: | ||
| y = yaml.safe_load(response.text) | ||
| y = {i['tag_id']: i for i in y['tags']} | ||
| return y | ||
| else: | ||
| raise Exception(f"Failed to fetch OLI tags from Github: {response.status_code} - {response.text}") | ||
| def get_OLI_value_sets(self) -> dict: | ||
| """ | ||
| Get latest value sets for OLI tags. | ||
| Returns: | ||
| dict: Dictionary of value sets with tag_id as key | ||
| """ | ||
| value_sets = {} | ||
| # Extract value sets from tag definitions (must be a list) | ||
| for tag_def in self.oli.tag_definitions.values(): | ||
| if 'schema' not in tag_def: | ||
| continue | ||
| schema = tag_def['schema'] | ||
| tag_id = tag_def['tag_id'] | ||
| value_set = None | ||
| # Get enum from direct schema or array items | ||
| if 'enum' in schema: | ||
| value_set = schema['enum'] | ||
| elif (schema.get('type') == 'array' and | ||
| 'items' in schema and | ||
| 'enum' in schema['items']): | ||
| value_set = schema['items']['enum'] | ||
| # Process and add to value_sets | ||
| if value_set and isinstance(value_set, list): | ||
| value_sets[tag_id] = [i.lower() if isinstance(i, str) else i for i in value_set] | ||
| # value set for owner_project | ||
| url = "https://api.growthepie.com/v1/labels/projects.json" | ||
| response = requests.get(url) | ||
| if response.status_code == 200: | ||
| y = yaml.safe_load(response.text) | ||
| value_sets["owner_project"] = [i[0] for i in y['data']['data']] | ||
| value_sets["owner_project"] = [i.lower() if isinstance(i, str) else i for i in value_sets["owner_project"]] | ||
| else: | ||
| raise Exception(f"Failed to fetch owner_project value set from grwothepie projects api: {response.status_code} - {response.text}") | ||
| # value set for usage_category | ||
| url = "https://raw.githubusercontent.com/openlabelsinitiative/OLI/refs/heads/main/1_label_schema/tags/valuesets/usage_category.yml" | ||
| response = requests.get(url) | ||
| if response.status_code == 200: | ||
| y = yaml.safe_load(response.text) | ||
| value_sets['usage_category'] = [i['category_id'] for i in y['categories']] | ||
| value_sets['usage_category'] = [i.lower() if isinstance(i, str) else i for i in value_sets['usage_category']] | ||
| else: | ||
| raise Exception(f"Failed to fetch usage_category value set from OLI Github: {response.status_code} - {response.text}") | ||
| return value_sets | ||
| def get_full_raw_export_parquet(self, file_path: str="raw_labels.parquet") -> str: | ||
| """ | ||
| Downloads the full raw export of all labels in the OLI Label Pool as a Parquet file. | ||
| Args: | ||
| file_path (str): Path where the file will be saved. Defaults to "raw_labels.parquet". | ||
| Returns: | ||
| str: Path to the downloaded Parquet file | ||
| """ | ||
| url = "https://api.growthepie.com/v1/oli/labels_raw.parquet" | ||
| response = requests.get(url, stream=True) | ||
| if response.status_code == 200: | ||
| with open(file_path, 'wb') as f: | ||
| f.write(response.content) | ||
| print(f"Downloaded and saved: {file_path}") | ||
| return file_path | ||
| else: | ||
| print(f"Failed to download {url}. Status code: {response.status_code}") | ||
| return None | ||
| def get_full_decoded_export_parquet(self, file_path: str="decoded_labels.parquet") -> str: | ||
| """ | ||
| Downloads the full decoded export of all labels in the OLI Label Pool as a Parquet file. | ||
| Args: | ||
| file_path (str): Path where the file will be saved. Defaults to "decoded_labels.parquet". | ||
| Returns: | ||
| str: Path to the downloaded Parquet file | ||
| """ | ||
| url = "https://api.growthepie.com/v1/oli/labels_decoded.parquet" | ||
| response = requests.get(url, stream=True) | ||
| if response.status_code == 200: | ||
| with open(file_path, 'wb') as f: | ||
| f.write(response.content) | ||
| print(f"Downloaded and saved: {file_path}") | ||
| return file_path | ||
| else: | ||
| print(f"Failed to download {url}. Status code: {response.status_code}") | ||
| return None |
| import requests | ||
| import json | ||
| class GraphQLClient: | ||
| def __init__(self, oli_client): | ||
| """ | ||
| Initialize the GraphQLClient with an OLI client. | ||
| Args: | ||
| oli_client: The OLI client instance | ||
| """ | ||
| self.oli = oli_client | ||
| def graphql_query_attestations(self, address: str=None, attester: str=None, timeCreated: int=None, revocationTime: int=None, take: int=None, id: str=None, expand_json: bool=True) -> dict: | ||
| """ | ||
| Queries attestations from the EAS GraphQL API based on the specified filters. | ||
| Args: | ||
| address (str, optional): Ethereum address of the labeled contract | ||
| attester (str, optional): Ethereum address of the attester | ||
| timeCreated (int, optional): Filter for attestations created after this timestamp | ||
| revocationTime (int, optional): Filter for attestations with revocation time >= this timestamp | ||
| take (int, optional): Maximum number of attestations to return | ||
| id (str, optional): Specific attestation ID to filter by | ||
| expand_json (bool, default: True): Whether to expand decodedDataJson fields in the response | ||
| Returns: | ||
| dict: JSON response containing matching attestation data | ||
| """ | ||
| query = """ | ||
| query Attestations($take: Int, $where: AttestationWhereInput, $orderBy: [AttestationOrderByWithRelationInput!]) { | ||
| attestations(take: $take, where: $where, orderBy: $orderBy) { | ||
| attester | ||
| decodedDataJson | ||
| expirationTime | ||
| id | ||
| ipfsHash | ||
| isOffchain | ||
| recipient | ||
| refUID | ||
| revocable | ||
| revocationTime | ||
| revoked | ||
| time | ||
| timeCreated | ||
| txid | ||
| } | ||
| } | ||
| """ | ||
| variables = { | ||
| "where": { | ||
| "schemaId": { | ||
| "equals": self.oli.oli_label_pool_schema | ||
| } | ||
| }, | ||
| "orderBy": [ | ||
| { | ||
| "timeCreated": "desc" | ||
| } | ||
| ] | ||
| } | ||
| # Add take to variables if not None | ||
| if take is not None: | ||
| variables["take"] = int(take) | ||
| # Add id to where clause if not None | ||
| if id is not None: | ||
| variables["where"]["id"] = {"equals": id} | ||
| # Add address to where clause if not None | ||
| if address is not None: | ||
| variables["where"]["recipient"] = {"equals": address} | ||
| # Add attester to where clause if not None | ||
| if attester is not None: | ||
| variables["where"]["attester"] = {"equals": attester} | ||
| # Add timeCreated to where clause if not None, ensuring it's an int | ||
| if timeCreated is not None: | ||
| timeCreated = int(timeCreated) | ||
| variables["where"]["timeCreated"] = {"gt": timeCreated} | ||
| # Add revocationTime to where clause if not None, ensuring it's an int | ||
| if revocationTime is not None: | ||
| revocationTime = int(revocationTime) | ||
| variables["where"]["revocationTime"] = {"gte": revocationTime} | ||
| headers = { | ||
| "Content-Type": "application/json" | ||
| } | ||
| response = requests.post(self.oli.graphql, json={"query": query, "variables": variables}, headers=headers) | ||
| if response.status_code == 200: | ||
| if expand_json: | ||
| # Expand decodedDataJson fields in the response | ||
| return self.graphql_expand_decoded_data_json(response.json()) | ||
| else: | ||
| # Return raw response if no expansion is wanted | ||
| return response.json() | ||
| else: | ||
| raise Exception(f"GraphQL query failed with status code {response.status_code}: {response.text}") | ||
| def graphql_expand_decoded_data_json(self, attestations_data: dict) -> list: | ||
| """ | ||
| Expand decodedDataJson fields in attestations data into separate columns. | ||
| Args: | ||
| attestations_data (dict): GraphQL response from oli.graphql_query_attestations() | ||
| Returns: | ||
| list: List of dictionaries with expanded decodedDataJson fields | ||
| """ | ||
| expanded_data = [] | ||
| for row in attestations_data['data']['attestations']: | ||
| # Start with the original row data | ||
| expanded_row = row.copy() | ||
| # Check if decodedDataJson exists and is not None | ||
| if 'decodedDataJson' in row and row['decodedDataJson']: | ||
| try: | ||
| # Parse the JSON string | ||
| if isinstance(row['decodedDataJson'], str): | ||
| decoded_data = json.loads(row['decodedDataJson']) | ||
| else: | ||
| decoded_data = row['decodedDataJson'] | ||
| # Extract each field from the decoded data | ||
| for item in decoded_data: | ||
| field_name = item['name'] | ||
| # Extract the actual value from the nested structure | ||
| if 'value' in item and 'value' in item['value']: | ||
| value = item['value']['value'] | ||
| # Handle BigNumber hex values | ||
| if isinstance(value, dict) and value.get('type') == 'BigNumber': | ||
| expanded_row[field_name] = int(value['hex'], 16) | ||
| # Handle empty arrays or objects | ||
| elif isinstance(value, (list, dict)) and not value: | ||
| expanded_row[field_name] = value | ||
| else: | ||
| expanded_row[field_name] = value | ||
| else: | ||
| expanded_row[field_name] = None | ||
| except (json.JSONDecodeError, KeyError, TypeError) as e: | ||
| # If parsing fails, keep original row and add error info | ||
| expanded_row['_parsing_error'] = str(e) | ||
| expanded_data.append(expanded_row) | ||
| return {'data': {'attestations': expanded_data}} |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
134143
61.75%20
5.26%1951
68.19%