oli-python 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,117 @@
1
+ Metadata-Version: 2.4
2
+ Name: oli-python
3
+ Version: 0.1.0
4
+ Summary: 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.
5
+ Home-page: https://github.com/openlabelsinitiative/oli-python
6
+ Author: Lorenz Lehmann
7
+ Author-email: lorenz@growthepie.xyz
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
+ Requires-Python: >=3.9
15
+ Description-Content-Type: text/markdown
16
+ Requires-Dist: web3>=6.0.0
17
+ Requires-Dist: PyYAML>=6.0.0
18
+ Dynamic: author
19
+ Dynamic: author-email
20
+ Dynamic: classifier
21
+ Dynamic: description
22
+ Dynamic: description-content-type
23
+ Dynamic: home-page
24
+ Dynamic: requires-dist
25
+ Dynamic: requires-python
26
+ Dynamic: summary
27
+
28
+ # OLI Python Package
29
+
30
+ 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.
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install oli-python
36
+ ```
37
+
38
+ ## Basic Usage
39
+
40
+ ```python
41
+ from oli import OLI
42
+ import os
43
+
44
+ # Initialize the client
45
+ # Make sure to pull in your private key from an .env file
46
+ oli = OLI(private_key=os.environ['private_key'], is_production=True)
47
+
48
+ # Create an offchain label
49
+ address = ""
50
+ chain_id = "eip155:1" # Ethereum
51
+ tags = {
52
+ "contract_name": "growthepie donation address",
53
+ "is_eoa": True,
54
+ "owner_project": "growthepie"
55
+ }
56
+
57
+ # Check if your label is OLI compliant
58
+ possible_to_attest = oli.check_label_correctness(address, chain_id, tags)
59
+ print(f"You can attest your label: {possible_to_attest}")
60
+
61
+ # Submit a label as an offchain attestation
62
+ response = oli.create_offchain_label(address, chain_id, tags)
63
+ print(f"Attestation created: {response.text}")
64
+
65
+ # Submit a label as an onchain attestation
66
+ tx_hash, uid = oli.create_onchain_label(address, chain_id, tags)
67
+ print(f"Transaction hash: {tx_hash}")
68
+ print(f"Attestation UID: {uid}")
69
+
70
+ # Batch submit multiple labels as one onchain attestation
71
+ labels = [
72
+ {'address': address, 'chain_id': chain_id, 'tags': tags},
73
+ {'address': address, 'chain_id': chain_id, 'tags': tags}
74
+ ]
75
+ tx_hash, uids = oli.create_multi_onchain_labels(labels)
76
+ print(f"Batch transaction hash: {tx_hash}")
77
+ print(f"Attestation UIDs: {uids}")
78
+
79
+ # Revoke an attestation (revoking onchain attestations here)
80
+ trx_hash = oli.revoke_attestation(uid, onchain=True)
81
+
82
+ # Revoke multiple attestations (revoking onchain attestations here)
83
+ trx_hash, count = oli.multi_revoke_attestations(uids, onchain=True)
84
+
85
+ # Query attestations for a specific address
86
+ result = oli.graphql_query_attestations(address=address)
87
+ print(result)
88
+
89
+ # Download parquet export of raw attestations
90
+ oli.get_full_raw_export_parquet()
91
+
92
+ # Download parquet export of decoded attestations
93
+ oli.get_full_decoded_export_parquet()
94
+
95
+ ```
96
+
97
+ ## Wallet Requirements
98
+
99
+ Make sure your wallet contains ETH to pay for onchain attestations (including revocations). Offchain attestations are free.
100
+
101
+ The OLI Label Pool is deployed on **Base mainnet**. For testing purposes, you can use Base Sepolia Testnet (set `is_production=False`).
102
+
103
+ ## Features
104
+
105
+ - Create onchain (single or batch) and offchain (single) OLI label attestations
106
+ - Revoke attestations (single or batch)
107
+ - Check your label if it is OLI compliant
108
+ - Query attestations using GraphQL
109
+ - Download full dataset exports in Parquet format
110
+
111
+ ## Documentation
112
+
113
+ For more details, see the [OLI Documentation](https://github.com/openlabelsinitiative/OLI).
114
+
115
+ ## License
116
+
117
+ MIT
@@ -0,0 +1,90 @@
1
+ # OLI Python Package
2
+
3
+ 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.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install oli-python
9
+ ```
10
+
11
+ ## Basic Usage
12
+
13
+ ```python
14
+ from oli import OLI
15
+ import os
16
+
17
+ # Initialize the client
18
+ # Make sure to pull in your private key from an .env file
19
+ oli = OLI(private_key=os.environ['private_key'], is_production=True)
20
+
21
+ # Create an offchain label
22
+ address = ""
23
+ chain_id = "eip155:1" # Ethereum
24
+ tags = {
25
+ "contract_name": "growthepie donation address",
26
+ "is_eoa": True,
27
+ "owner_project": "growthepie"
28
+ }
29
+
30
+ # Check if your label is OLI compliant
31
+ possible_to_attest = oli.check_label_correctness(address, chain_id, tags)
32
+ print(f"You can attest your label: {possible_to_attest}")
33
+
34
+ # Submit a label as an offchain attestation
35
+ response = oli.create_offchain_label(address, chain_id, tags)
36
+ print(f"Attestation created: {response.text}")
37
+
38
+ # Submit a label as an onchain attestation
39
+ tx_hash, uid = oli.create_onchain_label(address, chain_id, tags)
40
+ print(f"Transaction hash: {tx_hash}")
41
+ print(f"Attestation UID: {uid}")
42
+
43
+ # Batch submit multiple labels as one onchain attestation
44
+ labels = [
45
+ {'address': address, 'chain_id': chain_id, 'tags': tags},
46
+ {'address': address, 'chain_id': chain_id, 'tags': tags}
47
+ ]
48
+ tx_hash, uids = oli.create_multi_onchain_labels(labels)
49
+ print(f"Batch transaction hash: {tx_hash}")
50
+ print(f"Attestation UIDs: {uids}")
51
+
52
+ # Revoke an attestation (revoking onchain attestations here)
53
+ trx_hash = oli.revoke_attestation(uid, onchain=True)
54
+
55
+ # Revoke multiple attestations (revoking onchain attestations here)
56
+ trx_hash, count = oli.multi_revoke_attestations(uids, onchain=True)
57
+
58
+ # Query attestations for a specific address
59
+ result = oli.graphql_query_attestations(address=address)
60
+ print(result)
61
+
62
+ # Download parquet export of raw attestations
63
+ oli.get_full_raw_export_parquet()
64
+
65
+ # Download parquet export of decoded attestations
66
+ oli.get_full_decoded_export_parquet()
67
+
68
+ ```
69
+
70
+ ## Wallet Requirements
71
+
72
+ Make sure your wallet contains ETH to pay for onchain attestations (including revocations). Offchain attestations are free.
73
+
74
+ The OLI Label Pool is deployed on **Base mainnet**. For testing purposes, you can use Base Sepolia Testnet (set `is_production=False`).
75
+
76
+ ## Features
77
+
78
+ - Create onchain (single or batch) and offchain (single) OLI label attestations
79
+ - Revoke attestations (single or batch)
80
+ - Check your label if it is OLI compliant
81
+ - Query attestations using GraphQL
82
+ - Download full dataset exports in Parquet format
83
+
84
+ ## Documentation
85
+
86
+ For more details, see the [OLI Documentation](https://github.com/openlabelsinitiative/OLI).
87
+
88
+ ## License
89
+
90
+ MIT
@@ -0,0 +1,4 @@
1
+ from oli.core import OLI
2
+
3
+ __version__ = "0.1.0"
4
+ __all__ = ["OLI"]
@@ -0,0 +1,11 @@
1
+ from oli.attestation.onchain import OnchainAttestations
2
+ from oli.attestation.offchain import OffchainAttestations
3
+ from oli.attestation.utils_validator import UtilsValidator
4
+ from oli.attestation.utils_other import UtilsOther
5
+
6
+ __all__ = [
7
+ "OnchainAttestations",
8
+ "OffchainAttestations",
9
+ "UtilsValidator",
10
+ "UtilsOther"
11
+ ]
@@ -0,0 +1,273 @@
1
+ import time
2
+ import requests
3
+ import secrets
4
+ import json
5
+ from requests import Response
6
+
7
+ class OffchainAttestations:
8
+ def __init__(self, oli_client):
9
+ """
10
+ Initialize OffchainAttestations with an OLI client.
11
+
12
+ Args:
13
+ oli_client: The OLI client instance
14
+ """
15
+ self.oli = oli_client
16
+
17
+ def create_offchain_label(self, address: str, chain_id: str, tags: dict, ref_uid: str="0x0000000000000000000000000000000000000000000000000000000000000000", retry: int=4):
18
+ """
19
+ Create an offchain OLI label attestation for a contract.
20
+
21
+ Args:
22
+ address (str): The contract address to label
23
+ chain_id (str): Chain ID in CAIP-2 format where the address/contract resides
24
+ tags (dict): OLI compliant tags as a dict information (name, version, etc.)
25
+ ref_uid (str): Reference UID
26
+ retry (int): Number of retries for the API post request to EAS ipfs
27
+
28
+ Returns:
29
+ dict: API request response
30
+ """
31
+ # fix simple formatting errors in tags
32
+ tags = self.oli.validator.fix_simple_tags_formatting(tags)
33
+
34
+ # Check all necessary input parameters
35
+ self.oli.validator.check_label_correctness(address, chain_id, tags, ref_uid, auto_fix=False)
36
+
37
+ # Encode the label data
38
+ data = self.oli.utils_other.encode_label_data(chain_id, tags)
39
+
40
+ # Build the attestation
41
+ attestation = self.build_offchain_attestation(
42
+ recipient=address,
43
+ schema=self.oli.oli_label_pool_schema,
44
+ data=data,
45
+ ref_uid=ref_uid
46
+ )
47
+
48
+ # Post to the API & retry if status code is not 200
49
+ response = self.post_offchain_attestation(attestation)
50
+ n0 = retry
51
+ while response.status_code != 200 and retry > 0:
52
+ retry -= 1
53
+ time.sleep(2 ** (n0 - retry)) # exponential backoff
54
+ response = self.post_offchain_attestation(attestation)
55
+
56
+ # if it fails after all retries, raise an error
57
+ if response.status_code != 200:
58
+ raise Exception(f"Failed to submit offchain attestation to EAS API ipfs post endpoint after {n0} retries: {response.status_code} - {response.text}")
59
+
60
+ return response
61
+
62
+ def post_offchain_attestation(self, attestation: dict, filename: str="OLI.txt") -> Response:
63
+ """
64
+ Post API an attestation to the EAS API.
65
+
66
+ Args:
67
+ attestation (dict): The attestation package
68
+ filename (str): Custom filename
69
+
70
+ Returns:
71
+ dict: API response
72
+ """
73
+ # Convert numerical values to strings for JSON serialization
74
+ attestation["sig"]["message"]["time"] = str(attestation["sig"]["message"]["time"])
75
+ attestation["sig"]["message"]["expirationTime"] = str(attestation["sig"]["message"]["expirationTime"])
76
+ attestation["sig"]["domain"]["chainId"] = str(attestation["sig"]["domain"]["chainId"])
77
+
78
+ # Prepare payload for the API endpoint
79
+ payload = {
80
+ "filename": filename,
81
+ "textJson": json.dumps(attestation, separators=(',', ':'))
82
+ }
83
+
84
+ headers = {
85
+ "Content-Type": "application/json"
86
+ }
87
+
88
+ # Post the data to the API
89
+ response = requests.post(self.oli.eas_api_url, json=payload, headers=headers)
90
+ return response
91
+
92
+ def build_offchain_attestation(self, recipient: str, schema: str, data: str, ref_uid: str, revocable: bool=True, expiration_time: int=0) -> dict:
93
+ """
94
+ Build an offchain attestation with the given parameters.
95
+
96
+ Args:
97
+ recipient (str): Ethereum address of the contract to be labeled
98
+ schema (str): Schema hash
99
+ data (str): Hex-encoded data
100
+ ref_uid (str): Reference UID
101
+ revocable (bool): Whether the attestation is revocable
102
+ expiration_time (int): Expiration time in seconds since epoch
103
+
104
+ Returns:
105
+ dict: The signed attestation and UID
106
+ """
107
+ # Create a random salt
108
+ salt = f"0x{secrets.token_hex(32)}"
109
+
110
+ # Current time in seconds
111
+ current_time = int(time.time())
112
+
113
+ # Typed data for the attestation
114
+ typed_data = {
115
+ "version": 2,
116
+ "recipient": recipient,
117
+ "time": current_time,
118
+ "revocable": revocable,
119
+ "schema": schema,
120
+ "refUID": ref_uid,
121
+ "data": data,
122
+ "expirationTime": expiration_time,
123
+ "salt": salt,
124
+ }
125
+
126
+ # EIP-712 typed data format
127
+ types = {
128
+ "domain": {
129
+ "name": "EAS Attestation",
130
+ "version": "1.2.0",
131
+ "chainId": self.oli.rpc_chain_number,
132
+ "verifyingContract": self.oli.eas_address
133
+ },
134
+ "primaryType": "Attest",
135
+ "message": typed_data,
136
+ "types": {
137
+ "Attest": [
138
+ {"name": "version", "type": "uint16"},
139
+ {"name": "schema", "type": "bytes32"},
140
+ {"name": "recipient", "type": "address"},
141
+ {"name": "time", "type": "uint64"},
142
+ {"name": "expirationTime", "type": "uint64"},
143
+ {"name": "revocable", "type": "bool"},
144
+ {"name": "refUID", "type": "bytes32"},
145
+ {"name": "data", "type": "bytes"},
146
+ {"name": "salt", "type": "bytes32"}
147
+ ]
148
+ }
149
+ }
150
+
151
+ # Sign the message using the account
152
+ signed_message = self.oli.account.sign_typed_data(
153
+ domain_data=types["domain"],
154
+ message_types=types["types"],
155
+ message_data=typed_data
156
+ )
157
+
158
+ # Calculate the UID
159
+ attester = '0x0000000000000000000000000000000000000000' # for offchain UID calculation
160
+ uid = self.oli.utils_other.calculate_attestation_uid_v2(
161
+ schema, recipient, attester, current_time, data,
162
+ expiration_time, revocable, ref_uid, salt=salt
163
+ )
164
+ uid_hex = '0x' + uid.hex()
165
+
166
+ # Package the result
167
+ result = {
168
+ "sig": {
169
+ "domain": types["domain"],
170
+ "primaryType": types["primaryType"],
171
+ "types": types["types"],
172
+ "message": typed_data,
173
+ "uid": uid_hex,
174
+ "version": 2,
175
+ "signature": {
176
+ "r": hex(signed_message.r),
177
+ "s": hex(signed_message.s),
178
+ "v": signed_message.v
179
+ }
180
+ },
181
+ "signer": self.oli.address
182
+ }
183
+
184
+ return result
185
+
186
+ def revoke_attestation(self, uid_hex: str, gas_limit: int=200000):
187
+ """
188
+ Revoke an offchain attestation using its UID.
189
+
190
+ Args:
191
+ uid_hex (str): UID of the attestation to revoke (in hex format)
192
+ gas_limit (int): Gas limit for the transaction. If not set, defaults to 200000. Gas estimation is not possible for revoke transactions.
193
+
194
+ Returns:
195
+ str: Transaction hash
196
+ """
197
+ function = self.oli.eas.functions.revokeOffchain(self.oli.w3.to_bytes(hexstr=uid_hex))
198
+
199
+ # Define the transaction parameters
200
+ tx_params = {
201
+ 'chainId': self.oli.rpc_chain_number,
202
+ 'gasPrice': self.oli.w3.eth.gas_price,
203
+ 'nonce': self.oli.w3.eth.get_transaction_count(self.oli.address),
204
+ }
205
+
206
+ # Estimate gas if no limit provided
207
+ tx_params = self.oli.utils_other.estimate_gas_limit(function, tx_params, gas_limit)
208
+
209
+ # Build the transaction to revoke an attestation
210
+ transaction = function.build_transaction(tx_params)
211
+
212
+ # Sign the transaction
213
+ signed_txn = self.oli.w3.eth.account.sign_transaction(transaction, private_key=self.oli.private_key)
214
+
215
+ # Send the transaction
216
+ try:
217
+ txn_hash = self.oli.w3.eth.send_raw_transaction(signed_txn.raw_transaction)
218
+ except Exception as e:
219
+ raise Exception(f"Failed to send revoke transaction to mempool: {e}")
220
+
221
+ # Get the transaction receipt
222
+ txn_receipt = self.oli.w3.eth.wait_for_transaction_receipt(txn_hash)
223
+
224
+ # Check if the transaction was successful
225
+ if txn_receipt.status == 1:
226
+ return f"0x{txn_hash.hex()}"
227
+ else:
228
+ raise Exception(f"Transaction failed: {txn_receipt}")
229
+
230
+ def multi_revoke_attestations(self, uids: str, gas_limit: int=10000000):
231
+ """
232
+ Revoke multiple offchain attestations in a single transaction.
233
+
234
+ Args:
235
+ uids (list): List of UIDs to revoke (in hex format)
236
+ gas_limit (int): Gas limit for the transaction. If not set, defaults to 10000000. Gas estimation is not possible for revoke transactions.
237
+
238
+ Returns:
239
+ str: Transaction hash
240
+ int: Number of attestations revoked
241
+ """
242
+ revocation_data = []
243
+ for uid in uids:
244
+ revocation_data.append(self.oli.w3.to_bytes(hexstr=uid))
245
+ function = self.oli.eas.functions.multiRevokeOffchain(revocation_data)
246
+
247
+ # Define the transaction parameters
248
+ tx_params = {
249
+ 'chainId': self.oli.rpc_chain_number,
250
+ 'gasPrice': self.oli.w3.eth.gas_price,
251
+ 'nonce': self.oli.w3.eth.get_transaction_count(self.oli.address),
252
+ }
253
+
254
+ # Estimate gas if no limit provided
255
+ tx_params = self.oli.utils_other.estimate_gas_limit(function, tx_params, gas_limit)
256
+
257
+ # Build the transaction
258
+ transaction = function.build_transaction(tx_params)
259
+
260
+ # Sign the transaction
261
+ signed_txn = self.oli.w3.eth.account.sign_transaction(transaction, private_key=self.oli.private_key)
262
+
263
+ # Send the transaction
264
+ txn_hash = self.oli.w3.eth.send_raw_transaction(signed_txn.raw_transaction)
265
+
266
+ # Get the transaction receipt
267
+ txn_receipt = self.oli.w3.eth.wait_for_transaction_receipt(txn_hash)
268
+
269
+ # Check if the transaction was successful
270
+ if txn_receipt.status == 1:
271
+ return f"0x{txn_hash.hex()}", len(uids)
272
+ else:
273
+ raise Exception(f"Transaction failed: {txn_receipt}")