|
5 | 5 |
|
6 | 6 | from eth_account import Account |
7 | 7 | from eth_account.messages import encode_typed_data |
| 8 | +from eth_utils.crypto import keccak |
8 | 9 | from web3 import Web3 |
9 | 10 |
|
10 | 11 | from virtuals_acp.abis.job_manager import JOB_MANAGER_ABI |
11 | 12 | from virtuals_acp.abis.memo_manager import MEMO_MANAGER_ABI |
12 | 13 | from virtuals_acp.alchemy import AlchemyAccountKit |
13 | 14 | from virtuals_acp.configs.configs import ACPContractConfig, BASE_MAINNET_CONFIG_V2 |
| 15 | +from virtuals_acp.constants import SINGLE_SIGNER_VALIDATION_MODULE_ADDRESS |
14 | 16 | from virtuals_acp.contract_clients.base_contract_client import BaseAcpContractClient |
15 | 17 | from virtuals_acp.exceptions import ACPError |
16 | 18 | from virtuals_acp.models import AcpJobX402PaymentDetails, OffChainJob, OperationPayload, X402PayableRequest, X402PayableRequirements, X402Payment |
@@ -202,19 +204,34 @@ def get_asset_manager_address(self) -> str: |
202 | 204 | return self.memo_manager_contract.functions.assetManager().call() |
203 | 205 |
|
204 | 206 | def sign_typed_data(self, typed_data: dict[str, Any]) -> str: |
205 | | - domain = typed_data["domain"] |
206 | | - types = typed_data["types"] |
207 | | - primary_type = typed_data["primaryType"] |
208 | | - message = typed_data["message"] |
209 | | - |
210 | | - # encode_typed_data expects (domain_data, types, primary_type, message_data) |
211 | | - # It handles EIP-712 hashing internally |
212 | | - signable = encode_typed_data( |
213 | | - domain, |
214 | | - types, |
215 | | - primary_type, |
216 | | - message, |
217 | | - ) |
218 | | - |
| 207 | + encoded = encode_typed_data(full_message=typed_data) |
| 208 | + typed_data_hash = keccak(b"\x19\x01" + encoded.header + encoded.body) |
| 209 | + |
| 210 | + replay_safe_typed_data = { |
| 211 | + "domain": { |
| 212 | + "chainId": self.config.chain_id, |
| 213 | + "verifyingContract": SINGLE_SIGNER_VALIDATION_MODULE_ADDRESS, |
| 214 | + "salt": "0x" + "00" * 12 + self.agent_wallet_address[2:], |
| 215 | + }, |
| 216 | + "types": {"ReplaySafeHash": [{"name": "hash", "type": "bytes32"}]}, |
| 217 | + "message": {"hash": "0x" + typed_data_hash.hex()}, |
| 218 | + "primaryType": "ReplaySafeHash", |
| 219 | + } |
| 220 | + |
| 221 | + signable = encode_typed_data(full_message=replay_safe_typed_data) |
219 | 222 | signed = self.account.sign_message(signable) |
220 | | - return signed.signature.hex() |
| 223 | + raw_signature = signed.signature.hex() |
| 224 | + return f"0x{self._pack_1271_eoa_signature(raw_signature)}" |
| 225 | + |
| 226 | + def _pack_1271_eoa_signature(self, validation_signature: str) -> str: |
| 227 | + if validation_signature.startswith("0x"): |
| 228 | + validation_signature = validation_signature[2:] |
| 229 | + |
| 230 | + prefix = b"\x00" |
| 231 | + entity_id_bytes = self.entity_id.to_bytes(4, "big") |
| 232 | + separator = b"\xff" |
| 233 | + eoa_type = b"\x00" |
| 234 | + sig_bytes = bytes.fromhex(validation_signature) |
| 235 | + |
| 236 | + packed = prefix + entity_id_bytes + separator + eoa_type + sig_bytes |
| 237 | + return packed.hex() |
0 commit comments