oli-python 0.1.0__py3-none-any.whl
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.
- oli/__init__.py +4 -0
- oli/attestation/__init__.py +11 -0
- oli/attestation/offchain.py +273 -0
- oli/attestation/onchain.py +272 -0
- oli/attestation/utils_other.py +111 -0
- oli/attestation/utils_validator.py +200 -0
- oli/core.py +128 -0
- oli/data/__init__.py +4 -0
- oli/data/fetcher.py +111 -0
- oli/data/graphql.py +87 -0
- oli_python-0.1.0.dist-info/METADATA +117 -0
- oli_python-0.1.0.dist-info/RECORD +14 -0
- oli_python-0.1.0.dist-info/WHEEL +5 -0
- oli_python-0.1.0.dist-info/top_level.txt +1 -0
oli/__init__.py
ADDED
|
@@ -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}")
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
class OnchainAttestations:
|
|
2
|
+
def __init__(self, oli_client):
|
|
3
|
+
"""
|
|
4
|
+
Initialize OnchainAttestations with an OLI client.
|
|
5
|
+
|
|
6
|
+
Args:
|
|
7
|
+
oli_client: The OLI client instance
|
|
8
|
+
"""
|
|
9
|
+
self.oli = oli_client
|
|
10
|
+
|
|
11
|
+
def create_onchain_label(self, address: str, chain_id: str, tags: dict, ref_uid: str="0x0000000000000000000000000000000000000000000000000000000000000000", gas_limit: int=0) -> tuple[str, str]:
|
|
12
|
+
"""
|
|
13
|
+
Create an onchain OLI label attestation for a contract.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
address (str): The contract address to label
|
|
17
|
+
chain_id (str): Chain ID in CAIP-2 format where the address/contract resides
|
|
18
|
+
tags (dict): OLI compliant tags as a dict information (name, version, etc.)
|
|
19
|
+
ref_uid (str): Reference UID
|
|
20
|
+
gas_limit (int): Gas limit for the transaction. If set to 0, the function will estimate the gas limit.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
str: Transaction hash
|
|
24
|
+
str: UID of the attestation
|
|
25
|
+
"""
|
|
26
|
+
# fix simple formatting errors in tags
|
|
27
|
+
tags = self.oli.validator.fix_simple_tags_formatting(tags)
|
|
28
|
+
|
|
29
|
+
# Check all necessary input parameters
|
|
30
|
+
self.oli.validator.check_label_correctness(address, chain_id, tags, ref_uid, auto_fix=False)
|
|
31
|
+
|
|
32
|
+
# Encode the label data
|
|
33
|
+
data = self.oli.utils_other.encode_label_data(chain_id, tags)
|
|
34
|
+
|
|
35
|
+
# Create the attestation
|
|
36
|
+
function = self.oli.eas.functions.attest({
|
|
37
|
+
'schema': self.oli.w3.to_bytes(hexstr=self.oli.oli_label_pool_schema),
|
|
38
|
+
'data': {
|
|
39
|
+
'recipient': self.oli.w3.to_checksum_address(address),
|
|
40
|
+
'expirationTime': 0,
|
|
41
|
+
'revocable': True,
|
|
42
|
+
'refUID': self.oli.w3.to_bytes(hexstr=ref_uid),
|
|
43
|
+
'data': self.oli.w3.to_bytes(hexstr=data),
|
|
44
|
+
'value': 0
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
# Define the transaction parameters
|
|
49
|
+
tx_params = {
|
|
50
|
+
'chainId': self.oli.rpc_chain_number,
|
|
51
|
+
'gasPrice': self.oli.w3.eth.gas_price,
|
|
52
|
+
'nonce': self.oli.w3.eth.get_transaction_count(self.oli.address),
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# Estimate gas if no limit provided
|
|
56
|
+
tx_params = self.oli.utils_other.estimate_gas_limit(function, tx_params, gas_limit)
|
|
57
|
+
|
|
58
|
+
# Build the transaction to attest one label
|
|
59
|
+
transaction = function.build_transaction(tx_params)
|
|
60
|
+
|
|
61
|
+
# Sign the transaction with the private key
|
|
62
|
+
signed_txn = self.oli.w3.eth.account.sign_transaction(transaction, private_key=self.oli.private_key)
|
|
63
|
+
|
|
64
|
+
# Send the transaction
|
|
65
|
+
try:
|
|
66
|
+
txn_hash = self.oli.w3.eth.send_raw_transaction(signed_txn.raw_transaction)
|
|
67
|
+
except Exception as e:
|
|
68
|
+
raise Exception(f"Failed to send transaction to mempool: {e}")
|
|
69
|
+
|
|
70
|
+
# Wait for the transaction receipt
|
|
71
|
+
txn_receipt = self.oli.w3.eth.wait_for_transaction_receipt(txn_hash)
|
|
72
|
+
|
|
73
|
+
# Check if the transaction was successful
|
|
74
|
+
if txn_receipt.status == 1:
|
|
75
|
+
return f"0x{txn_hash.hex()}", f"0x{txn_receipt.logs[0].data.hex()}"
|
|
76
|
+
else:
|
|
77
|
+
raise Exception(f"Transaction failed onchain: {txn_receipt}")
|
|
78
|
+
|
|
79
|
+
def create_multi_onchain_labels(self, labels: list, gas_limit: int=0) -> tuple[str, list]:
|
|
80
|
+
"""
|
|
81
|
+
Batch submit OLI labels in one transaction.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
labels (list): List of labels, containing dictionaries with 'address', 'tags', and 'chain_id' (, optional 'ref_uid')
|
|
85
|
+
address (str): The contract address to label
|
|
86
|
+
chain_id (str): Chain ID in CAIP-2 format where the address/contract resides
|
|
87
|
+
tags (dict): OLI compliant tags as a dict information (name, version, etc.)
|
|
88
|
+
ref_uid (str): Reference UID
|
|
89
|
+
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.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
str: Transaction hash
|
|
93
|
+
list: List of UID of the attestation
|
|
94
|
+
"""
|
|
95
|
+
# Prepare the list of "data" requests
|
|
96
|
+
full_data = []
|
|
97
|
+
for label in labels:
|
|
98
|
+
# check if address, chain_id & tags are provided
|
|
99
|
+
if 'chain_id' not in label:
|
|
100
|
+
raise ValueError("chain_id must be provided for each label in CAIP-2 format (e.g., Base -> 'eip155:8453')")
|
|
101
|
+
elif 'address' not in label:
|
|
102
|
+
raise ValueError("An address must be provided for each label")
|
|
103
|
+
elif 'tags' not in label:
|
|
104
|
+
raise ValueError("tags dictionary must be provided for each label")
|
|
105
|
+
|
|
106
|
+
# fix simple formatting errors in tags
|
|
107
|
+
label['tags'] = self.oli.validator.fix_simple_tags_formatting(label['tags'])
|
|
108
|
+
|
|
109
|
+
# run checks on each label
|
|
110
|
+
self.oli.validator.check_label_correctness(label['address'], label['chain_id'], label['tags'], auto_fix=False)
|
|
111
|
+
|
|
112
|
+
# check if ref_uid is provided
|
|
113
|
+
if 'ref_uid' not in label:
|
|
114
|
+
label['ref_uid'] = "0x0000000000000000000000000000000000000000000000000000000000000000"
|
|
115
|
+
else:
|
|
116
|
+
self.oli.validator.checks_ref_uid(label['ref_uid'])
|
|
117
|
+
|
|
118
|
+
# ABI encode data for each attestation
|
|
119
|
+
data = self.oli.utils_other.encode_label_data(label['chain_id'], label['tags'])
|
|
120
|
+
full_data.append({
|
|
121
|
+
'recipient': self.oli.w3.to_checksum_address(label['address']),
|
|
122
|
+
'expirationTime': 0,
|
|
123
|
+
'revocable': True,
|
|
124
|
+
'refUID': self.oli.w3.to_bytes(hexstr=label['ref_uid']),
|
|
125
|
+
'data': self.oli.w3.to_bytes(hexstr=data),
|
|
126
|
+
'value': 0
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
# Create the multi-attestation request
|
|
130
|
+
multi_requests = [{
|
|
131
|
+
'schema': self.oli.w3.to_bytes(hexstr=self.oli.oli_label_pool_schema),
|
|
132
|
+
'data': full_data
|
|
133
|
+
}]
|
|
134
|
+
|
|
135
|
+
# Create the function call
|
|
136
|
+
function = self.oli.eas.functions.multiAttest(multi_requests)
|
|
137
|
+
|
|
138
|
+
# Define the transaction parameters
|
|
139
|
+
tx_params = {
|
|
140
|
+
'chainId': self.oli.rpc_chain_number,
|
|
141
|
+
'gasPrice': self.oli.w3.eth.gas_price,
|
|
142
|
+
'nonce': self.oli.w3.eth.get_transaction_count(self.oli.address),
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
# Estimate gas if no limit provided
|
|
146
|
+
tx_params = self.oli.utils_other.estimate_gas_limit(function, tx_params, gas_limit)
|
|
147
|
+
|
|
148
|
+
# Build the transaction to revoke an attestation
|
|
149
|
+
transaction = function.build_transaction(tx_params)
|
|
150
|
+
|
|
151
|
+
# Sign the transaction with the private key
|
|
152
|
+
signed_txn = self.oli.w3.eth.account.sign_transaction(transaction, private_key=self.oli.private_key)
|
|
153
|
+
|
|
154
|
+
# Send the transaction
|
|
155
|
+
try:
|
|
156
|
+
txn_hash = self.oli.w3.eth.send_raw_transaction(signed_txn.raw_transaction)
|
|
157
|
+
except Exception as e:
|
|
158
|
+
raise Exception(f"Failed to send transaction to mempool: {e}")
|
|
159
|
+
|
|
160
|
+
# Wait for the transaction receipt
|
|
161
|
+
txn_receipt = self.oli.w3.eth.wait_for_transaction_receipt(txn_hash)
|
|
162
|
+
|
|
163
|
+
# Check if the transaction was successful
|
|
164
|
+
if txn_receipt.status != 1:
|
|
165
|
+
raise Exception(f"Transaction failed onchain: {txn_receipt}")
|
|
166
|
+
|
|
167
|
+
# log the UIDs of the attestations in a list
|
|
168
|
+
uids = ['0x' + log.data.hex() for log in txn_receipt.logs]
|
|
169
|
+
|
|
170
|
+
return f"0x{txn_hash.hex()}", uids
|
|
171
|
+
|
|
172
|
+
def revoke_attestation(self, uid_hex: str, gas_limit: int=200000) -> str:
|
|
173
|
+
"""
|
|
174
|
+
Revoke an onchain attestation using its UID.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
uid_hex (str): UID of the attestation to revoke (in hex format)
|
|
178
|
+
gas_limit (int): Gas limit for the transaction. If not set, defaults to 200000. Gas estimation is not possible for revoke transactions.
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
str: Transaction hash
|
|
182
|
+
"""
|
|
183
|
+
function = self.oli.eas.functions.revoke({
|
|
184
|
+
'schema': self.oli.w3.to_bytes(hexstr=self.oli.oli_label_pool_schema),
|
|
185
|
+
'data': {
|
|
186
|
+
'uid': self.oli.w3.to_bytes(hexstr=uid_hex),
|
|
187
|
+
'value': 0
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
# Define the transaction parameters
|
|
192
|
+
tx_params = {
|
|
193
|
+
'chainId': self.oli.rpc_chain_number,
|
|
194
|
+
'gasPrice': self.oli.w3.eth.gas_price,
|
|
195
|
+
'nonce': self.oli.w3.eth.get_transaction_count(self.oli.address),
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
# Estimate gas if no limit provided
|
|
199
|
+
tx_params = self.oli.utils_other.estimate_gas_limit(function, tx_params, gas_limit)
|
|
200
|
+
|
|
201
|
+
# Build the transaction to revoke an attestation
|
|
202
|
+
transaction = function.build_transaction(tx_params)
|
|
203
|
+
|
|
204
|
+
# Sign the transaction
|
|
205
|
+
signed_txn = self.oli.w3.eth.account.sign_transaction(transaction, private_key=self.oli.private_key)
|
|
206
|
+
|
|
207
|
+
# Send the transaction
|
|
208
|
+
try:
|
|
209
|
+
txn_hash = self.oli.w3.eth.send_raw_transaction(signed_txn.raw_transaction)
|
|
210
|
+
except Exception as e:
|
|
211
|
+
raise Exception(f"Failed to send revoke transaction to mempool: {e}")
|
|
212
|
+
|
|
213
|
+
# Get the transaction receipt
|
|
214
|
+
txn_receipt = self.oli.w3.eth.wait_for_transaction_receipt(txn_hash)
|
|
215
|
+
|
|
216
|
+
# Check if the transaction was successful
|
|
217
|
+
if txn_receipt.status == 1:
|
|
218
|
+
return f"0x{txn_hash.hex()}"
|
|
219
|
+
else:
|
|
220
|
+
raise Exception(f"Transaction failed: {txn_receipt}")
|
|
221
|
+
|
|
222
|
+
def multi_revoke_attestations(self, uids: list, gas_limit: int=10000000) -> tuple[str, int]:
|
|
223
|
+
"""
|
|
224
|
+
Revoke multiple onchain attestations in a single transaction.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
uids (list): List of UIDs to revoke (in hex format)
|
|
228
|
+
gas_limit (int): Gas limit for the transaction. If not set, defaults to 10000000. Gas estimation is not possible for revoke transactions.
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
str: Transaction hash
|
|
232
|
+
int: Number of attestations revoked
|
|
233
|
+
"""
|
|
234
|
+
revocation_data = []
|
|
235
|
+
for uid in uids:
|
|
236
|
+
revocation_data.append({
|
|
237
|
+
'uid': self.oli.w3.to_bytes(hexstr=uid),
|
|
238
|
+
'value': 0
|
|
239
|
+
})
|
|
240
|
+
multi_requests = [{
|
|
241
|
+
'schema': self.oli.w3.to_bytes(hexstr=self.oli.oli_label_pool_schema),
|
|
242
|
+
'data': revocation_data
|
|
243
|
+
}]
|
|
244
|
+
function = self.oli.eas.functions.multiRevoke(multi_requests)
|
|
245
|
+
|
|
246
|
+
# Define the transaction parameters
|
|
247
|
+
tx_params = {
|
|
248
|
+
'chainId': self.oli.rpc_chain_number,
|
|
249
|
+
'gasPrice': self.oli.w3.eth.gas_price,
|
|
250
|
+
'nonce': self.oli.w3.eth.get_transaction_count(self.oli.address),
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
# Estimate gas if no limit provided
|
|
254
|
+
tx_params = self.oli.utils_other.estimate_gas_limit(function, tx_params, gas_limit)
|
|
255
|
+
|
|
256
|
+
# Build the transaction
|
|
257
|
+
transaction = function.build_transaction(tx_params)
|
|
258
|
+
|
|
259
|
+
# Sign the transaction
|
|
260
|
+
signed_txn = self.oli.w3.eth.account.sign_transaction(transaction, private_key=self.oli.private_key)
|
|
261
|
+
|
|
262
|
+
# Send the transaction
|
|
263
|
+
txn_hash = self.oli.w3.eth.send_raw_transaction(signed_txn.raw_transaction)
|
|
264
|
+
|
|
265
|
+
# Get the transaction receipt
|
|
266
|
+
txn_receipt = self.oli.w3.eth.wait_for_transaction_receipt(txn_hash)
|
|
267
|
+
|
|
268
|
+
# Check if the transaction was successful
|
|
269
|
+
if txn_receipt.status == 1:
|
|
270
|
+
return f"0x{txn_hash.hex()}", len(uids)
|
|
271
|
+
else:
|
|
272
|
+
raise Exception(f"Transaction failed: {txn_receipt}")
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from eth_abi.abi import encode
|
|
3
|
+
import secrets
|
|
4
|
+
from web3 import Web3
|
|
5
|
+
|
|
6
|
+
class UtilsOther:
|
|
7
|
+
def __init__(self, oli_client):
|
|
8
|
+
"""
|
|
9
|
+
Initialize the DataEncoder with an OLI client.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
oli_client: The OLI client instance
|
|
13
|
+
"""
|
|
14
|
+
self.oli = oli_client
|
|
15
|
+
|
|
16
|
+
def encode_label_data(self, chain_id: str, tags_json: dict) -> str:
|
|
17
|
+
"""
|
|
18
|
+
Encode label data in the OLI format.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
chain_id (str): Chain ID in CAIP-2 format of the label (e.g. 'eip155:8453')
|
|
22
|
+
tags_json (dict): Dictionary of tag data following the OLI format
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
str: Hex-encoded ABI data
|
|
26
|
+
"""
|
|
27
|
+
# Convert dict to JSON string if needed
|
|
28
|
+
if isinstance(tags_json, dict):
|
|
29
|
+
tags_json = json.dumps(tags_json)
|
|
30
|
+
|
|
31
|
+
# ABI encode the data
|
|
32
|
+
encoded_data = encode(['string', 'string'], [chain_id, tags_json])
|
|
33
|
+
return f"0x{encoded_data.hex()}"
|
|
34
|
+
|
|
35
|
+
def estimate_gas_limit(self, function, tx_params: dict, gas_limit: int) -> dict:
|
|
36
|
+
"""
|
|
37
|
+
Estimate gas for a transaction.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
function: The function to estimate gas for
|
|
41
|
+
tx_params (dict): Transaction parameters
|
|
42
|
+
gas_limit (int): Gas limit
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
tx_params (dict): Transaction parameters with estimated 'gas' field
|
|
46
|
+
"""
|
|
47
|
+
try:
|
|
48
|
+
if gas_limit == 0:
|
|
49
|
+
# Estimate gas with a buffer (e.g., 10% more than the estimate)
|
|
50
|
+
estimated_gas = function.estimate_gas(tx_params)
|
|
51
|
+
tx_params["gas"] = int(estimated_gas * 1.1) # Add 10% buffer
|
|
52
|
+
else:
|
|
53
|
+
tx_params["gas"] = gas_limit
|
|
54
|
+
except Exception as e:
|
|
55
|
+
tx_params["gas"] = 10000000 # Default fallback
|
|
56
|
+
return tx_params
|
|
57
|
+
|
|
58
|
+
def calculate_attestation_uid_v2(self, schema: str, recipient: str, attester: str, timestamp: int, data: str, expiration_time: int=0, revocable: bool=True, ref_uid: str="0x0000000000000000000000000000000000000000000000000000000000000000", bump: int=0, salt: str=None) -> bytes:
|
|
59
|
+
"""
|
|
60
|
+
Calculate the UID for an offchain attestation (v2).
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
schema (str): Schema hash
|
|
64
|
+
recipient (str): Recipient address
|
|
65
|
+
attester (str): Attester address
|
|
66
|
+
timestamp (int): Timestamp
|
|
67
|
+
data (str): Attestation data
|
|
68
|
+
expiration_time (int): Expiration time
|
|
69
|
+
revocable (bool): Whether attestation is revocable
|
|
70
|
+
ref_uid (str): Reference UID
|
|
71
|
+
bump (int): Bump value
|
|
72
|
+
salt (str): Salt value
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
bytes: The calculated UID
|
|
76
|
+
"""
|
|
77
|
+
# Generate salt if not provided
|
|
78
|
+
if salt is None:
|
|
79
|
+
salt = f"0x{secrets.token_hex(32)}"
|
|
80
|
+
|
|
81
|
+
# Version
|
|
82
|
+
version = 2
|
|
83
|
+
version_bytes = version.to_bytes(2, byteorder='big')
|
|
84
|
+
|
|
85
|
+
# Handle schema formatting
|
|
86
|
+
if not schema.startswith('0x'):
|
|
87
|
+
schema = '0x' + schema
|
|
88
|
+
schema_utf8_bytes = schema.encode('utf-8')
|
|
89
|
+
schema_bytes = schema_utf8_bytes
|
|
90
|
+
|
|
91
|
+
# Convert values to bytes
|
|
92
|
+
recipient_bytes = Web3.to_bytes(hexstr=recipient)
|
|
93
|
+
attester_bytes = Web3.to_bytes(hexstr=attester)
|
|
94
|
+
timestamp_bytes = timestamp.to_bytes(8, byteorder='big')
|
|
95
|
+
expiration_bytes = expiration_time.to_bytes(8, byteorder='big')
|
|
96
|
+
revocable_bytes = bytes([1]) if revocable else bytes([0])
|
|
97
|
+
ref_uid_bytes = Web3.to_bytes(hexstr=ref_uid)
|
|
98
|
+
data_bytes = Web3.to_bytes(hexstr=data)
|
|
99
|
+
salt_bytes = Web3.to_bytes(hexstr=salt)
|
|
100
|
+
bump_bytes = bump.to_bytes(4, byteorder='big')
|
|
101
|
+
|
|
102
|
+
# Pack all values
|
|
103
|
+
packed_data = (
|
|
104
|
+
version_bytes + schema_bytes + recipient_bytes + attester_bytes +
|
|
105
|
+
timestamp_bytes + expiration_bytes + revocable_bytes + ref_uid_bytes +
|
|
106
|
+
data_bytes + salt_bytes + bump_bytes
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Calculate keccak256 hash
|
|
110
|
+
uid = Web3.keccak(packed_data)
|
|
111
|
+
return uid
|