From 6b39d9c828cc0da5cf4516d0039f587241d0442f Mon Sep 17 00:00:00 2001 From: SebastianBoehler <27767932+SebastianBoehler@users.noreply.github.com> Date: Thu, 21 May 2026 20:28:56 +0200 Subject: [PATCH] fix(auth): honor funder address in CLOB auth headers --- docs/clob-v2-migration.md | 3 +++ include/order_signer.hpp | 4 +++- src/order_signer.cpp | 16 ++++++++-------- tests/test_order_signer_v2.cpp | 17 +++++++++++++++++ 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/docs/clob-v2-migration.md b/docs/clob-v2-migration.md index 2f8ac6f..f2b09a2 100644 --- a/docs/clob-v2-migration.md +++ b/docs/clob-v2-migration.md @@ -36,6 +36,9 @@ Implemented changes: creation params. - `POLY_1271` deposit-wallet signatures use the V2 wrapper format from the official V2 SDK. +- L1/L2 auth header generation honors the configured funder/proxy address so + API-key derivation and authenticated requests use the same wallet identity as + `POLY_1271` orders. - `POLY_1271` signer/API-key rejections are classified as `SdkErrorCode::DepositWalletSetup` with guidance to check the deployed funder wallet, approvals, balance, and CLOB balance sync. diff --git a/include/order_signer.hpp b/include/order_signer.hpp index c33eb76..0d7288a 100644 --- a/include/order_signer.hpp +++ b/include/order_signer.hpp @@ -145,7 +145,9 @@ namespace polymarket // L1 auth helpers std::array hash_clob_auth_domain(); - std::array hash_clob_auth(const std::string ×tamp, uint64_t nonce); + std::array hash_clob_auth(const std::string &address, + const std::string ×tamp, + uint64_t nonce); }; // Utility functions diff --git a/src/order_signer.cpp b/src/order_signer.cpp index d7fdff2..b9581a3 100644 --- a/src/order_signer.cpp +++ b/src/order_signer.cpp @@ -331,13 +331,15 @@ namespace polymarket return keccak256(encoded); } - std::array OrderSigner::hash_clob_auth(const std::string ×tamp, uint64_t nonce) + std::array OrderSigner::hash_clob_auth(const std::string &address, + const std::string ×tamp, + uint64_t nonce) { auto type_hash = keccak256(std::string( "ClobAuth(address address,string timestamp,uint256 nonce,string message)")); // Encode address (padded to 32 bytes) - auto addr_bytes = from_hex(address_); + auto addr_bytes = from_hex(address); std::vector addr_padded(32, 0); std::memcpy(addr_padded.data() + 12, addr_bytes.data(), std::min(addr_bytes.size(), size_t(20))); @@ -369,17 +371,16 @@ namespace polymarket auto now = std::chrono::system_clock::now(); auto timestamp = std::chrono::duration_cast(now.time_since_epoch()).count(); std::string ts_str = std::to_string(timestamp); + const std::string auth_address = override_address.empty() ? address_ : override_address; auto domain_hash = hash_clob_auth_domain(); - auto struct_hash = hash_clob_auth(ts_str, nonce); + auto struct_hash = hash_clob_auth(auth_address, ts_str, nonce); auto message_hash = encode_eip712(domain_hash, struct_hash); std::string signature = sign_hash(message_hash); L1Headers headers; - // Always use signer address for L1 auth (even for proxy wallets) - // The override_address parameter is ignored for L1 - it's only for reference - headers.poly_address = address_; + headers.poly_address = auth_address; headers.poly_signature = signature; headers.poly_timestamp = ts_str; headers.poly_nonce = std::to_string(nonce); @@ -493,8 +494,7 @@ namespace polymarket std::string signature = base64_encode(hmac_vec, true); L2Headers headers; - // Always use signer address for L2 auth - the API key is associated with the signer - headers.poly_address = address_; + headers.poly_address = funder_address.empty() ? address_ : funder_address; headers.poly_timestamp = std::to_string(timestamp); headers.poly_api_key = creds.api_key; headers.poly_passphrase = creds.api_passphrase; diff --git a/tests/test_order_signer_v2.cpp b/tests/test_order_signer_v2.cpp index 48cc302..2da373d 100644 --- a/tests/test_order_signer_v2.cpp +++ b/tests/test_order_signer_v2.cpp @@ -55,6 +55,23 @@ int main() return 1; } + constexpr const char *kFunder = "0x1111111111111111111111111111111111111111"; + const auto l1_headers = signer.generate_l1_headers(0, kFunder); + if (!expect_equal("L1 auth funder address", l1_headers.poly_address, kFunder)) + { + return 1; + } + + ApiCredentials creds; + creds.api_key = "test-key"; + creds.api_secret = "c2VjcmV0"; + creds.api_passphrase = "test-passphrase"; + const auto l2_headers = signer.generate_l2_headers(creds, "GET", "/orders", "", kFunder); + if (!expect_equal("L2 auth funder address", l2_headers.poly_address, kFunder)) + { + return 1; + } + const auto eoa = signer.sign_order_with_salt(base_order(SignatureType::EOA), kExchangeV2, "123456789"); if (!expect_equal("EOA V2 signature", eoa.signature, "0x92daffe6e8b80fb13506e91647e066ff58d3f7050021043fac41a24990b279e33f3624578b7f0fc5a59b225d2d96f063c09147308e936d5a42527e369aff3d4a1c"))