Created
May 7, 2025 17:26
-
-
Save alexshchur/81c48d9a8f6c7e5c929b42155d7d5a6e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# pipenv install pysha3 | |
import sha3 # from pysha3 | |
def keccak256(data: bytes) -> bytes: | |
return sha3.keccak_256(data).digest() | |
def encode_address(addr: str) -> bytes: | |
return bytes.fromhex(addr[2:].rjust(64, '0')) | |
def encode_uint256(value: int) -> bytes: | |
return value.to_bytes(32, byteorder="big") | |
def encode_bytes(hex_data: str) -> bytes: | |
assert hex_data.startswith("0x") | |
return keccak256(bytes.fromhex(hex_data[2:])) | |
def encode_string(value: str) -> bytes: | |
return keccak256(value.encode("utf-8")) | |
def encode_type_string(primary_type: str, types: dict) -> str: | |
fields = types[primary_type] | |
return f"{primary_type}(" + ",".join(f"{f['type']} {f['name']}" for f in fields) + ")" | |
def encode_struct_hash(primary_type: str, types: dict, data: dict) -> bytes: | |
type_str = encode_type_string(primary_type, types) | |
type_hash = keccak256(type_str.encode("utf-8")) | |
encoded_fields = [] | |
for field in types[primary_type]: | |
name = field["name"] | |
field_type = field["type"] | |
value = data[name] | |
if field_type == "address": | |
encoded_fields.append(encode_address(value)) | |
elif field_type == "uint256": | |
encoded_fields.append(encode_uint256(int(value))) | |
elif field_type == "bytes": | |
encoded_fields.append(encode_bytes(value)) | |
elif field_type == "string": | |
encoded_fields.append(encode_string(value)) | |
else: | |
raise ValueError(f"Unsupported field type: {field_type}") | |
return keccak256(type_hash + b''.join(encoded_fields)) | |
def encode_domain_separator(domain: dict) -> bytes: | |
TYPE_HASH_RAW = "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" | |
TYPE_HASH = keccak256(TYPE_HASH_RAW.encode("utf-8")) | |
HASHED_NAME = keccak256(domain["name"].encode("utf-8")) | |
HASHED_VERSION = keccak256(domain["version"].encode("utf-8")) | |
CHAIN_ID = encode_uint256(domain["chainId"]) | |
VERIFYING_CONTRACT = encode_address(domain["verifyingContract"]) | |
return keccak256(TYPE_HASH + HASHED_NAME + HASHED_VERSION + CHAIN_ID + VERIFYING_CONTRACT) | |
def compute_eip712_body(domain: dict, types: dict, primary_type: str, message: dict) -> bytes: | |
domain_separator = encode_domain_separator(domain) | |
print("domain_separator", domain_separator.hex()) | |
struct_hash = encode_struct_hash(primary_type, types, message) | |
print("struct hash", struct_hash.hex()) | |
return keccak256(b"\x19\x01" + domain_separator + struct_hash) | |
# === EIP-712 Order Type Definition === | |
ORDER_EIP_712_TYPES = { | |
"Order": [ | |
{"name": "owner", "type": "address"}, | |
{"name": "beneficiary", "type": "address"}, | |
{"name": "srcToken", "type": "address"}, | |
{"name": "destToken", "type": "address"}, | |
{"name": "srcAmount", "type": "uint256"}, | |
{"name": "destAmount", "type": "uint256"}, | |
{"name": "expectedDestAmount", "type": "uint256"}, | |
{"name": "deadline", "type": "uint256"}, | |
{"name": "nonce", "type": "uint256"}, | |
{"name": "partnerAndFee", "type": "uint256"}, | |
{"name": "permit", "type": "bytes"}, | |
] | |
} | |
order = { | |
"owner": "0x12924049e2d21664e35387c69429c98e9891a820", | |
"beneficiary": "0x12924049e2d21664e35387c69429c98e9891a820", | |
"srcToken": "0x04c154b66cb340f3ae24111cc767e0184ed00cc6", | |
"destToken": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", | |
"srcAmount": "1202939820354578", | |
"destAmount": "868930905657826", | |
"expectedDestAmount": "873297392620931", | |
"deadline": "1740790593", | |
"nonce": "1740787014424", | |
"partnerAndFee": "90631063861114836560958097440945986548822432573276877133894239693005947666432", | |
"permit": "0x00000000000000000000000012924049e2d21664e35387c69429c98e9891a8200000000000000000000000000000000000bbf5c5fd284e657f01bd000933c96d0000000000000000000000000000000000000000000000000004461140adac120000000000000000000000000000000000000000000000000000000067c641b2000000000000000000000000000000000000000000000000000000000000001bae25978476dcaf13eb21c5140c058bc49fd4d087f0f0d23b1ccede8f9288bb33450886d34539305ede3448c44206e89074d91afcdc8e07cabbe23cf45c67dd7c" | |
} | |
domain = { | |
"name": "Portikus", | |
"version": "2.0.0", | |
"chainId": 1, | |
"verifyingContract": "0x0000000000bbf5c5fd284e657f01bd000933c96d" | |
} | |
# === Compute and Print === | |
body_hash = compute_eip712_body(domain, ORDER_EIP_712_TYPES, "Order", order) | |
print("EIP-712 signing hash (.body):", body_hash.hex()) |
compute struct hash: https://dune.com/queries/5098965
full snippet:
i.e.
keccak256("\x19\x01" || _domainSeparatorV4() || structHash(order));
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
compute domain separator: https://dune.com/queries/5098950