Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions src/polymarket/clients/async_secure.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,6 @@ async def create(
credentials: ApiKeyCreds | None = None,
api_key: ApiKey | None = None,
nonce: int = 0,
validate_credentials: bool = True,
logger: logging.Logger | None = None,
) -> Self:
"""Create an authenticated async client.
Expand All @@ -268,12 +267,35 @@ async def create(
derived during client creation.
api_key: Optional key for gasless wallet and relayed transaction workflows.
nonce: Credential derivation nonce. Cannot be combined with ``credentials``.
validate_credentials: Whether provided credentials should be validated.

Raises:
UserInputError: If key material, wallet, nonce, or credentials are invalid.
RequestRejectedError: If credential derivation or validation is rejected.
"""
return await cls._create(
private_key=private_key,
wallet=wallet,
environment=environment,
credentials=credentials,
api_key=api_key,
nonce=nonce,
validate_credentials=True,
logger=logger,
)

@classmethod
async def _create(
cls,
*,
private_key: str,
wallet: str | None = None,
environment: Environment = PRODUCTION,
credentials: ApiKeyCreds | None = None,
api_key: ApiKey | None = None,
nonce: int = 0,
validate_credentials: bool = True,
logger: logging.Logger | None = None,
) -> Self:
if not private_key:
raise UserInputError("private_key is required")
_validate_nonce(nonce)
Expand Down
26 changes: 24 additions & 2 deletions src/polymarket/clients/secure.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,6 @@ def create(
credentials: ApiKeyCreds | None = None,
api_key: ApiKey | None = None,
nonce: int = 0,
validate_credentials: bool = True,
logger: logging.Logger | None = None,
) -> Self:
"""Create an authenticated synchronous client.
Expand All @@ -242,12 +241,35 @@ def create(
derived during client creation.
api_key: Optional key for gasless wallet and relayed transaction workflows.
nonce: Credential derivation nonce. Cannot be combined with ``credentials``.
validate_credentials: Whether provided credentials should be validated.

Raises:
UserInputError: If key material, wallet, nonce, or credentials are invalid.
RequestRejectedError: If credential derivation or validation is rejected.
"""
return cls._create(
private_key=private_key,
wallet=wallet,
environment=environment,
credentials=credentials,
api_key=api_key,
nonce=nonce,
validate_credentials=True,
logger=logger,
)

@classmethod
def _create(
cls,
*,
private_key: str,
wallet: str | None = None,
environment: Environment = PRODUCTION,
credentials: ApiKeyCreds | None = None,
api_key: ApiKey | None = None,
nonce: int = 0,
validate_credentials: bool = True,
logger: logging.Logger | None = None,
) -> Self:
if not private_key:
raise UserInputError("private_key is required")
_validate_nonce(nonce)
Expand Down
3 changes: 2 additions & 1 deletion tests/integration/test_clob_reads.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# pyright: reportPrivateUsage=false
import asyncio
from decimal import Decimal

Expand Down Expand Up @@ -40,7 +41,7 @@ def test_async_secure_get_midpoint_returns_decimal_in_unit_range(
active_clob_token: TokenId,
) -> None:
async def run() -> Decimal:
client = await AsyncSecureClient.create(
client = await AsyncSecureClient._create(
private_key=PRIVATE_KEY,
wallet=SIGNER_ADDRESS,
credentials=FAKE_CREDS,
Expand Down
18 changes: 9 additions & 9 deletions tests/unit/_relayer_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ async def make_deposit_client() -> AsyncSecureClient:

signer = Account.from_key(PK_DEPLOY_WALLET)
wallet = derive_uups_deposit_wallet_address(signer.address, PRODUCTION.wallet_derivation)
return await AsyncSecureClient.create(
return await AsyncSecureClient._create(
private_key=PK_DEPLOY_WALLET,
wallet=wallet,
credentials=FAKE_CREDS,
Expand All @@ -48,7 +48,7 @@ async def make_proxy_client() -> AsyncSecureClient:

signer = Account.from_key(PK_PROXY_WALLET)
wallet = derive_proxy_wallet_address(signer.address, PRODUCTION.wallet_derivation)
return await AsyncSecureClient.create(
return await AsyncSecureClient._create(
private_key=PK_PROXY_WALLET,
wallet=wallet,
credentials=FAKE_CREDS,
Expand All @@ -61,7 +61,7 @@ async def make_eoa_client(*, with_api_key: bool = True) -> AsyncSecureClient:
from eth_account import Account

signer = Account.from_key(PK_DEPLOY_WALLET)
return await AsyncSecureClient.create(
return await AsyncSecureClient._create(
private_key=PK_DEPLOY_WALLET,
wallet=signer.address,
credentials=FAKE_CREDS,
Expand All @@ -80,7 +80,7 @@ async def make_eoa_client_with_rpc(

env = dataclasses.replace(PRODUCTION, rpc_url="https://rpc.test")
signer = Account.from_key(PK_DEPLOY_WALLET)
client = await AsyncSecureClient.create(
client = await AsyncSecureClient._create(
private_key=PK_DEPLOY_WALLET,
wallet=signer.address,
credentials=FAKE_CREDS,
Expand Down Expand Up @@ -153,7 +153,7 @@ async def make_safe_client() -> AsyncSecureClient:

signer = Account.from_key(PK_SAFE_WALLET)
wallet = derive_safe_wallet_address(signer.address, PRODUCTION.wallet_derivation)
return await AsyncSecureClient.create(
return await AsyncSecureClient._create(
private_key=PK_SAFE_WALLET,
wallet=wallet,
credentials=FAKE_CREDS,
Expand Down Expand Up @@ -226,7 +226,7 @@ def make_sync_eoa_client(*, with_api_key: bool = True) -> SecureClient:
from eth_account import Account

signer = Account.from_key(PK_DEPLOY_WALLET)
return SecureClient.create(
return SecureClient._create(
private_key=PK_DEPLOY_WALLET,
wallet=signer.address,
credentials=FAKE_CREDS,
Expand All @@ -243,7 +243,7 @@ def make_sync_deposit_client() -> SecureClient:

signer = Account.from_key(PK_DEPLOY_WALLET)
wallet = derive_uups_deposit_wallet_address(signer.address, PRODUCTION.wallet_derivation)
return SecureClient.create(
return SecureClient._create(
private_key=PK_DEPLOY_WALLET,
wallet=wallet,
credentials=FAKE_CREDS,
Expand All @@ -260,7 +260,7 @@ def make_sync_proxy_client() -> SecureClient:

signer = Account.from_key(PK_PROXY_WALLET)
wallet = derive_proxy_wallet_address(signer.address, PRODUCTION.wallet_derivation)
return SecureClient.create(
return SecureClient._create(
private_key=PK_PROXY_WALLET,
wallet=wallet,
credentials=FAKE_CREDS,
Expand All @@ -277,7 +277,7 @@ def make_sync_safe_client() -> SecureClient:

signer = Account.from_key(PK_SAFE_WALLET)
wallet = derive_safe_wallet_address(signer.address, PRODUCTION.wallet_derivation)
return SecureClient.create(
return SecureClient._create(
private_key=PK_SAFE_WALLET,
wallet=wallet,
credentials=FAKE_CREDS,
Expand Down
28 changes: 14 additions & 14 deletions tests/unit/test_account_transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def _assert_l2_headers(request: httpx.Request) -> None:

def _make_client() -> AsyncSecureClient:
return asyncio.run(
AsyncSecureClient.create(
AsyncSecureClient._create(
private_key=PRIVATE_KEY,
wallet=SIGNER_ADDRESS,
credentials=FAKE_CREDS,
Expand All @@ -109,7 +109,7 @@ def test_get_closed_only_mode_returns_bool_and_uses_l2_headers() -> None:
captured: list[httpx.Request] = []

async def run() -> bool:
client = await AsyncSecureClient.create(
client = await AsyncSecureClient._create(
private_key=PRIVATE_KEY,
wallet=SIGNER_ADDRESS,
credentials=FAKE_CREDS,
Expand Down Expand Up @@ -147,7 +147,7 @@ def handler(request: httpx.Request) -> httpx.Response:
return httpx.Response(200, json=next(responses), request=request)

async def run() -> list[str]:
client = await AsyncSecureClient.create(
client = await AsyncSecureClient._create(
private_key=PRIVATE_KEY,
wallet=SIGNER_ADDRESS,
credentials=FAKE_CREDS,
Expand Down Expand Up @@ -177,7 +177,7 @@ def test_get_order_targets_data_order_path() -> None:
captured: list[httpx.Request] = []

async def run() -> str:
client = await AsyncSecureClient.create(
client = await AsyncSecureClient._create(
private_key=PRIVATE_KEY,
wallet=SIGNER_ADDRESS,
credentials=FAKE_CREDS,
Expand Down Expand Up @@ -209,7 +209,7 @@ def handler(request: httpx.Request) -> httpx.Response:
return httpx.Response(200, json=next(responses), request=request)

async def run() -> list[str]:
client = await AsyncSecureClient.create(
client = await AsyncSecureClient._create(
private_key=PRIVATE_KEY,
wallet=SIGNER_ADDRESS,
credentials=FAKE_CREDS,
Expand Down Expand Up @@ -239,7 +239,7 @@ def test_get_notifications_includes_signature_type_for_eoa_wallet() -> None:
captured: list[httpx.Request] = []

async def run() -> None:
client = await AsyncSecureClient.create(
client = await AsyncSecureClient._create(
private_key=PRIVATE_KEY,
wallet=SIGNER_ADDRESS,
credentials=FAKE_CREDS,
Expand All @@ -263,7 +263,7 @@ def test_drop_notifications_uses_delete_with_comma_separated_ids() -> None:
captured: list[httpx.Request] = []

async def run() -> None:
client = await AsyncSecureClient.create(
client = await AsyncSecureClient._create(
private_key=PRIVATE_KEY,
wallet=SIGNER_ADDRESS,
credentials=FAKE_CREDS,
Expand All @@ -286,7 +286,7 @@ async def run() -> None:

def test_drop_notifications_rejects_empty_id_list() -> None:
async def run() -> None:
client = await AsyncSecureClient.create(
client = await AsyncSecureClient._create(
private_key=PRIVATE_KEY,
wallet=SIGNER_ADDRESS,
credentials=FAKE_CREDS,
Expand All @@ -305,7 +305,7 @@ def test_get_balance_allowance_for_collateral_omits_token_id() -> None:
captured: list[httpx.Request] = []

async def run() -> int:
client = await AsyncSecureClient.create(
client = await AsyncSecureClient._create(
private_key=PRIVATE_KEY,
wallet=SIGNER_ADDRESS,
credentials=FAKE_CREDS,
Expand Down Expand Up @@ -334,7 +334,7 @@ def test_get_balance_allowance_for_conditional_includes_token_id() -> None:
captured: list[httpx.Request] = []

async def run() -> None:
client = await AsyncSecureClient.create(
client = await AsyncSecureClient._create(
private_key=PRIVATE_KEY,
wallet=SIGNER_ADDRESS,
credentials=FAKE_CREDS,
Expand Down Expand Up @@ -366,7 +366,7 @@ def test_secure_client_classifies_eoa_when_wallet_equals_signer() -> None:

def test_secure_client_normalizes_wallet_to_checksum() -> None:
async def run() -> str:
client = await AsyncSecureClient.create(
client = await AsyncSecureClient._create(
private_key=PRIVATE_KEY,
wallet=SIGNER_ADDRESS.lower(),
credentials=FAKE_CREDS,
Expand All @@ -382,7 +382,7 @@ async def run() -> str:

def test_secure_client_rejects_invalid_wallet_address() -> None:
async def run() -> None:
await AsyncSecureClient.create(
await AsyncSecureClient._create(
private_key=PRIVATE_KEY,
wallet="not-an-address",
credentials=FAKE_CREDS,
Expand All @@ -400,7 +400,7 @@ def test_secure_client_defaults_wallet_to_signer_address() -> None:
expected = to_checksum_address(Account.from_key(PRIVATE_KEY).address)

async def run() -> str:
client = await AsyncSecureClient.create(
client = await AsyncSecureClient._create(
private_key=PRIVATE_KEY,
credentials=FAKE_CREDS,
validate_credentials=False,
Expand All @@ -416,7 +416,7 @@ async def run() -> str:

def test_secure_client_rejects_unrelated_wallet_address() -> None:
async def run() -> None:
await AsyncSecureClient.create(
await AsyncSecureClient._create(
private_key=PRIVATE_KEY,
wallet="0x0000000000000000000000000000000000000002",
credentials=FAKE_CREDS,
Expand Down
26 changes: 17 additions & 9 deletions tests/unit/test_auth_transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ async def run() -> ApiKeyCreds:

def test_async_secure_create_with_credentials_skips_auth_flow() -> None:
async def run() -> None:
client = await AsyncSecureClient.create(
client = await AsyncSecureClient._create(
private_key=PRIVATE_KEY,
wallet=SIGNER_ADDRESS,
credentials=FAKE_CREDS,
Expand All @@ -144,7 +144,7 @@ def test_fetch_api_keys_sends_l2_headers() -> None:
captured: list[httpx.Request] = []

async def run() -> tuple[str, ...]:
client = await AsyncSecureClient.create(
client = await AsyncSecureClient._create(
private_key=PRIVATE_KEY,
wallet=SIGNER_ADDRESS,
credentials=FAKE_CREDS,
Expand Down Expand Up @@ -173,7 +173,7 @@ def test_delete_api_key_succeeds_on_ok_response() -> None:
captured: list[httpx.Request] = []

async def run() -> None:
client = await AsyncSecureClient.create(
client = await AsyncSecureClient._create(
private_key=PRIVATE_KEY,
wallet=SIGNER_ADDRESS,
credentials=FAKE_CREDS,
Expand All @@ -193,7 +193,7 @@ async def run() -> None:

def test_delete_api_key_raises_unexpected_response_on_non_ok_payload() -> None:
async def run() -> None:
client = await AsyncSecureClient.create(
client = await AsyncSecureClient._create(
private_key=PRIVATE_KEY,
wallet=SIGNER_ADDRESS,
credentials=FAKE_CREDS,
Expand All @@ -211,7 +211,7 @@ async def run() -> None:

def test_fetch_api_keys_propagates_401_as_request_rejected() -> None:
async def run() -> tuple[str, ...]:
client = await AsyncSecureClient.create(
client = await AsyncSecureClient._create(
private_key=PRIVATE_KEY,
wallet=SIGNER_ADDRESS,
credentials=FAKE_CREDS,
Expand All @@ -232,7 +232,7 @@ def test_async_secure_create_rejects_credentials_with_nonzero_nonce() -> None:
from polymarket.errors import UserInputError

async def run() -> None:
await AsyncSecureClient.create(
await AsyncSecureClient._create(
private_key=PRIVATE_KEY, wallet=SIGNER_ADDRESS, credentials=FAKE_CREDS, nonce=1
)

Expand All @@ -244,7 +244,11 @@ def test_async_secure_create_rejects_negative_nonce() -> None:
from polymarket.errors import UserInputError

async def run() -> None:
await AsyncSecureClient.create(private_key=PRIVATE_KEY, wallet=SIGNER_ADDRESS, nonce=-1)
await AsyncSecureClient._create(
private_key=PRIVATE_KEY,
wallet=SIGNER_ADDRESS,
nonce=-1,
)

with pytest.raises(UserInputError, match="non-negative integer"):
asyncio.run(run())
Expand All @@ -254,7 +258,11 @@ def test_async_secure_create_rejects_bool_nonce() -> None:
from polymarket.errors import UserInputError

async def run() -> None:
await AsyncSecureClient.create(private_key=PRIVATE_KEY, wallet=SIGNER_ADDRESS, nonce=True) # type: ignore[arg-type]
await AsyncSecureClient._create(
private_key=PRIVATE_KEY,
wallet=SIGNER_ADDRESS,
nonce=True, # type: ignore[arg-type]
)

with pytest.raises(UserInputError, match="non-negative integer"):
asyncio.run(run())
Expand Down Expand Up @@ -298,7 +306,7 @@ def test_l2_signature_includes_canonical_body_for_authenticated_post() -> None:
captured: list[httpx.Request] = []

async def run() -> None:
client = await AsyncSecureClient.create(
client = await AsyncSecureClient._create(
private_key=PRIVATE_KEY,
wallet=SIGNER_ADDRESS,
credentials=FAKE_CREDS,
Expand Down
Loading
Loading