Current code
src/polymarket/_internal/actions/relayer/gasless.py, lines 351–357 (async) and 562–568 (sync):
payload = {
"type": RelayerTransactionType.WALLET_CREATE.value,
"from": ctx.signer.address,
"to": ctx.environment.wallet_derivation.deposit_wallet_factory,
"metadata": metadata,
}
response = await submit_gasless(ctx.relayer, payload=payload) # line 357 — no retry loop
Compare with prepare_gasless_transaction (lines 75–91), which wraps the same submit_gasless endpoint in a full retry loop:
for attempt in range(GASLESS_SUBMIT_RETRY_ATTEMPTS + 1):
try:
response = await _submit_for_wallet_type(ctx, payload, wallet_type)
break
except RateLimitError:
if attempt < GASLESS_SUBMIT_RETRY_ATTEMPTS:
await asyncio.sleep(GASLESS_SUBMIT_RETRY_DELAY)
continue
raise
except Exception as e:
if is_retryable_submit_error(e) and attempt < GASLESS_SUBMIT_RETRY_ATTEMPTS:
await asyncio.sleep(GASLESS_SUBMIT_RETRY_DELAY)
continue
raise
GASLESS_SUBMIT_RETRY_ATTEMPTS and is_retryable_submit_error are imported at lines 27–28 of the same file but are never referenced in either submit_deposit_wallet_create or submit_deposit_wallet_create_sync.
Reproduction
Call setup_gasless_wallet (secure.py:2027) when the relayer is under load or briefly rate-limiting:
- Relayer returns a
RateLimitError (HTTP 429) or a wallet-busy/nonce-mismatch 400 during the single submit_gasless call inside submit_deposit_wallet_create.
- The exception propagates immediately — no retry is attempted.
- The entire wallet-deployment flow fails, requiring the caller to restart from scratch.
With prepare_gasless_transaction the same error class is transparently retried up to GASLESS_SUBMIT_RETRY_ATTEMPTS times with the configured back-off, so the inconsistency is directly observable if you trigger both paths under the same transient relayer condition.
Impact
- Wallet deployment is disproportionately fragile compared to trade submission. A single transient 429 or wallet-busy response kills
setup_gasless_wallet entirely, while the same error during a regular gasless trade is silently retried.
- The
WALLET_CREATE payload has no nonce field (lines 351–356 / 562–567), making it fully idempotent — there is no correctness reason to avoid retrying it unchanged.
- All commits since the functions were introduced add only docs/credential/dependency changes; no commit has ever added retry logic to the
WALLET_CREATE path. The imported symbols going unused is a clear oversight.
Suggested fix
Wrap the submit_gasless call in both submit_deposit_wallet_create and submit_deposit_wallet_create_sync in the same retry pattern already used by prepare_gasless_transaction / prepare_gasless_transaction_sync (lines 392–408 for the sync counterpart). Since the payload is nonce-free it can be resubmitted unchanged on every attempt:
for attempt in range(GASLESS_SUBMIT_RETRY_ATTEMPTS + 1):
try:
response = await submit_gasless(ctx.relayer, payload=payload)
break
except RateLimitError:
if attempt < GASLESS_SUBMIT_RETRY_ATTEMPTS:
await asyncio.sleep(GASLESS_SUBMIT_RETRY_DELAY)
continue
raise
except Exception as e:
if is_retryable_submit_error(e) and attempt < GASLESS_SUBMIT_RETRY_ATTEMPTS:
await asyncio.sleep(GASLESS_SUBMIT_RETRY_DELAY)
continue
raise
Apply the equivalent synchronous pattern to submit_deposit_wallet_create_sync at line 568.
Related
prepare_gasless_transaction (async, lines 75–91) and prepare_gasless_transaction_sync (sync, lines 392–408) — the retry pattern to replicate
- Verified against HEAD commit 588d664; no open or closed issue/PR covers this path
Current code
src/polymarket/_internal/actions/relayer/gasless.py, lines 351–357 (async) and 562–568 (sync):Compare with
prepare_gasless_transaction(lines 75–91), which wraps the samesubmit_gaslessendpoint in a full retry loop:GASLESS_SUBMIT_RETRY_ATTEMPTSandis_retryable_submit_errorare imported at lines 27–28 of the same file but are never referenced in eithersubmit_deposit_wallet_createorsubmit_deposit_wallet_create_sync.Reproduction
Call
setup_gasless_wallet(secure.py:2027) when the relayer is under load or briefly rate-limiting:RateLimitError(HTTP 429) or a wallet-busy/nonce-mismatch 400 during the singlesubmit_gaslesscall insidesubmit_deposit_wallet_create.With
prepare_gasless_transactionthe same error class is transparently retried up toGASLESS_SUBMIT_RETRY_ATTEMPTStimes with the configured back-off, so the inconsistency is directly observable if you trigger both paths under the same transient relayer condition.Impact
setup_gasless_walletentirely, while the same error during a regular gasless trade is silently retried.WALLET_CREATEpayload has no nonce field (lines 351–356 / 562–567), making it fully idempotent — there is no correctness reason to avoid retrying it unchanged.WALLET_CREATEpath. The imported symbols going unused is a clear oversight.Suggested fix
Wrap the
submit_gaslesscall in bothsubmit_deposit_wallet_createandsubmit_deposit_wallet_create_syncin the same retry pattern already used byprepare_gasless_transaction/prepare_gasless_transaction_sync(lines 392–408 for the sync counterpart). Since the payload is nonce-free it can be resubmitted unchanged on every attempt:Apply the equivalent synchronous pattern to
submit_deposit_wallet_create_syncat line 568.Related
prepare_gasless_transaction(async, lines 75–91) andprepare_gasless_transaction_sync(sync, lines 392–408) — the retry pattern to replicate