Polymarket Relayer v2 — POST /submit возвращает 400 «плохих запросов» с Gnosis Safe + Privy кастодиальный кошелекPython

Программы на Python
Ответить
Anonymous
 Polymarket Relayer v2 — POST /submit возвращает 400 «плохих запросов» с Gnosis Safe + Privy кастодиальный кошелек

Сообщение Anonymous »

Контекст
Я создаю платформу, которая интегрируется с безгазовым релейным устройством Polymarket (

Код: Выделить всё

https://relayer-v2.polymarket.com/submit
), используя:
  • Безопасный прокси-кошелек Gnosis (развернутый через завод Polymarket 0xaacFeEa03eb1561C4e67d661e40682Bd20E3541b)
  • Privy в качестве ключа хранитель (нет доступа к необработанному закрытому ключу)
  • Код: Выделить всё

    py-builder-relayer-client==0.0.1
    [/b] (официальный SDK Polymarket)
  • Код: Выделить всё

    py-builder-signing-sdk
    [/b] для аутентификации HMAC
Цель – отправить 6 транзакций утверждения ERC-20/ERC-1155 в одном безгазовом пакете мультиотправки.

Что подтверждено, работает
  • Код: Выделить всё

    GET /transactions
    → 200 (учетные данные Builder действительны)
  • Код: Выделить всё

    GET /deployed?address=...
    → {"deployed": true (Safe развертывается на Polygon)
  • Код: Выделить всё

    GET /nonce
    → {"nonce": "0" (исправный текущий nonce)
  • Код: Выделить всё

    client.execute()
    с тестовым закрытым ключом (различный EOA/Safe) → 200 OK
  • Код: Выделить всё

    proxyWallet
    , полученный с помощью SDK, соответствует развернутому Safe on-chain
Проблема
Когда я внедряю собственный PrivySigner (который вызывает API Privy вместо использования локального закрытого ключа), POST /submit последовательно возвращает:

Код: Выделить всё

{"error": "bad request"}
Моя реализация PrivySigner
Класс Signer SDK подписывает следующим образом:

Код: Выделить всё

# SDK original (py-builder-relayer-client/signer.py)
def sign_eip712_struct_hash(self, message_hash):
msg = encode_defunct(HexBytes(message_hash))  # applies EIP-191 prefix
sig = Account.sign_message(msg, self.private_key).signature.hex()
return prepend_zx(sig)
Мой PrivySigner вместо этого вызывает API Privy:

Код: Выделить всё

class PrivySigner:
def __init__(self, privy_wallet_id: str, eoa_address: str, chain_id: int = 137):
self.privy_wallet_id = privy_wallet_id
self._address = to_checksum_address(eoa_address)

def address(self) -> str:
return self._address

def get_chain_id(self) -> int:
return self._chain_id

def sign(self, message_hash) -> str:
h = "0x" + message_hash.hex() if isinstance(message_hash, bytes) else str(message_hash)
return _run_sync(_privy_sign_raw(self.privy_wallet_id, h))

def sign_eip712_struct_hash(self, message_hash) -> str:
h = "0x" + message_hash.hex() if isinstance(message_hash, bytes) else str(message_hash)
# Using personal_sign with encoding=hex — intended to match encode_defunct behavior
sig_hex = _run_sync(_privy_sign_eip191(self.privy_wallet_id, h))
return sig_hex
Куда звонит _privy_sign_eip191:

Код: Выделить всё

async def _privy_sign_eip191(privy_wallet_id: str, hash_hex: str) -> str:
payload = {
"method": "personal_sign",
"params": {"message": hash_hex, "encoding": "hex"},
}
# ... POST to Privy API
Диагноз на данный момент
Я провел тест шифрования по хэшу структуры, вычисленному SDK:

Код: Выделить всё

from py_builder_relayer_client.builder.safe import create_struct_hash
from eth_account import Account
from eth_account.messages import encode_defunct
from hexbytes import HexBytes

struct_hash = "0x918fb835628db28a00da7606c72e46c127d45b1a7f0827499d0f5091c3d65185"
sig_from_privy_personal_sign = "0xd9965d01...1c"

# Test 1: HexBytes (same as SDK)
msg = encode_defunct(HexBytes(struct_hash))
recovered = Account.recover_message(msg, signature=sig_from_privy_personal_sign)
print(recovered)  # → 0xeBcC372BA40bF88e730e0C901114713428B20f49  ← WRONG

# Test 2: secp256k1_sign (raw, no EIP-191 prefix)
sig_raw = "0x3ea38967...1c"  # from Privy secp256k1_sign
recovered2 = Account._recover_hash(HexBytes(struct_hash), signature=sig_raw)
print(recovered2)  # → 0xD15b9f4Dab19808eb4F25AB647820cD8111cE1ef  ← CORRECT
Таким образом, личный_sign с кодировкой: hex через Privy не дает тот же результат, что и encode_defunct(HexBytes(hash)) из eth_account. secp256k1_sign (необработанный, без префикса) действительно восстанавливает правильного владельца.

Вопрос
Контракт Gnosis Safe (

Код: Выделить всё

GnosisSafeL2 v1.3.0
) в checkSignatures делает это для подписей EOA ( или v = 32):

Код: Выделить всё

currentOwner = ecrecover(
keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", dataHash)),
v - 4, r, s
);
So Safe применяет префикс EIP-191 внутри. Это означает, что переданная подпись должна быть необработанной подписью secp256k1 (без префикса), и Safe добавит префикс перед безопасным восстановлением.
Но Sign_eip712_struct_hash SDK использует encode_defunct (который добавляет префикс EIP-191 перед подписанием), а затем Split_and_pack_sig устанавливает v = v_raw + 4 (выдает v=31/32). Это будет означать, что Safe вычитает 4, получает v=27/28 и снова применяет префикс, что приводит к ситуации с двойным префиксом.
Почему SDK работает с обычным закрытым ключом, но создает неверные подписи, когда префикс EIP-191 применяется через личный_знак Privy?
Правильно ли использовать secp256k1_sign (необработанный хеш, без префикса), а затем установить v = v_raw + 27 перед передачей в Split_and_pack_sig? Или есть что-то еще в том, как Privy обрабатывает комбинацию Personal_sign + кодирование: hex, которая отличается от eth_account.encode_defunct?

Среда
  • Python 3.12
  • Код: Выделить всё

    py-builder-relayer-client==0.0.1
  • Код: Выделить всё

    eth-account==0.13.4
  • Polygon (идентификатор цепи 137)
  • Gnosis Safe v1.3.0 (GnosisSafeL2)
  • Приватный серверный API (кастодиальный кошелек)


Подробнее: https://stackoverflow.com/questions/799 ... nosis-safe
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Python»