Skip to content

feat(client): add collateral return plan/execute to secure clients - #200

Merged
kartojal merged 12 commits into
mainfrom
feat/collateral-return
Jul 24, 2026
Merged

feat(client): add collateral return plan/execute to secure clients#200
kartojal merged 12 commits into
mainfrom
feat/collateral-return

Conversation

@kartojal

@kartojal kartojal commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds Collateral Return support (DEV-445): a combos Protocol V2 position-lifecycle workflow that returns idle collateral to the wallet.

Sync

while True:
    plan = client.plan_collateral_return()

    handle = client.execute_collateral_return_plan(plan=plan)
    handle.wait()

    if not plan.truncated:
        break

Async

while True:
    plan = await client.plan_collateral_return()

    handle = await client.execute_collateral_return_plan(plan=plan)
    await handle.wait()

    if not plan.truncated:
        break

Available on both SecureClient and AsyncSecureClient (async is identical with await). Supports Deposit Wallet, Safe, and Proxy accounts; EOA-bound clients are rejected before anything is planned or signed.

What's included

  • CollateralReturnPlan — an inspectable execution artifact: Decimal collateral amounts, position_summary with consumed/created positions, and forward-compatible operation kinds (known kinds coerce to CollateralReturnOperationKind, unknown kinds parse as plain strings).
  • Execution submits the plan's router call exactly as planned — nothing is recomputed client-side. Missing trading approvals fail fast with MissingTradingApprovalsError before any signature; no approval transactions run implicitly. Stale plans raise CollateralReturnPlanRejectedError (never retried; recovery is a fresh plan), while transient submission errors re-sign with a fresh nonce.
  • Per-wallet-type envelope signing extracted into shared build_signed_*_payload helpers reused by the existing gasless path; transports gained an optional per-request timeout so only the plan call waits up to 2 minutes.

Tests

  • 18 unit tests: wire parsing (renames, base-unit decimals, omitted zero fields, unknown kinds), envelopes for all three wallet types, gates (EOA, wallet/chain mismatch, empty plan), fail-fast approval pre-check, 409 no-retry, and transient wallet-busy re-sign.
  • Live integration suite: plan assertions per wallet type, EOA rejection, empty-plan contract (skips while inventory is pending), and a metered seed → plan → execute → wait round trip (net-zero by construction) that finishes by re-submitting the executed plan and asserting it is rejected. All verified against production.

Note

Medium Risk
New on-chain execution path via signed router calls and relayer envelopes; mistakes in plan validation or signing could submit wrong transactions, though client-side wallet/chain checks and non-retry on 409 mitigate stale-plan risk.

Overview
Adds collateral return to sync and async secure clients: plan_collateral_return() calls the combos collateral-return service (120s read timeout) and returns a typed CollateralReturnPlanResponse; execute_collateral_return_plan(plan=...) signs the plan’s router_call and submits it to /v1/collateral-return/submit as a gasless transaction. Deposit Wallet, Safe, and Proxy are supported; EOAs are rejected up front; execution needs a Builder or Relayer API key and validates wallet/chain against the plan.

Wires a new combos transport (collateral_return_url on Environment), exports collateral-return models and shared e6 decimal validators, and refactors gasless signing into reusable build_signed_*_payload helpers plus submit_gasless_with_retry (collateral submit uses combos, not the relayer /submit). RelayerEnvelope NewType guards relayer payloads; post_json accepts an optional per-request timeout.

Reviewed by Cursor Bugbot for commit 6b4fa14. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread src/polymarket/_internal/actions/combos.py Outdated
Comment thread tests/integration/test_collateral_return_live.py Outdated
Comment thread tests/integration/test_collateral_return_live.py Outdated
kartojal and others added 4 commits July 24, 2026 14:35
…havior

- Rename the plan model to CollateralReturnPlanResponse with the ts-sdk
  field set: wire names (starting_pusd, net_pusd_out, final_pusd,
  required_pusd_input), restored operation_count, estimated_cost, and
  candidate_position_ids, and ts-matching required fields (only
  position_summary stays defaulted).
- Apply the 120s request timeout to submit as well as plan; submit
  re-validation also recomputes wallet state server-side.
- Drop CollateralReturnPlanRejectedError and the client-side empty-plan
  gate: stale and empty plans surface the service rejection as
  RequestRejectedError, matching ts-sdk.
- Share one gasless submit retry helper between prepare_gasless_transaction
  and collateral return execution instead of four copies of the loop.
- Move the decimal-string validators to a shared models module under public
  names; e6 amounts serialize back to base-unit strings in JSON mode so
  dumped plans re-validate to the same amounts, and signed amounts are
  rejected.
- Narrow client execute return types to the gasless handles.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 0c13450. Configure here.

Comment thread tests/integration/conftest.py
kartojal and others added 2 commits July 24, 2026 18:27
Live plan assertions keep only service semantics the model cannot check
(wallet, chain, router target, hash format); type re-assertions, the
wire-exponent check, and empty-plan restatements are removed. The empty-plan
scenario now mirrors ts-sdk: skip on inventory, assert no operations, expect
the service rejection.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Payload builders return RelayerEnvelope and the submit functions require it,
so arbitrary dicts cannot reach a submit endpoint without going through a
builder.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kartojal
kartojal merged commit b4a1b9f into main Jul 24, 2026
7 checks passed
@kartojal
kartojal deleted the feat/collateral-return branch July 24, 2026 17:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants