Skip to content

feat(client): support directional collateral return - #428

Open
adi-poly wants to merge 9 commits into
mainfrom
feat/directional-support
Open

adi-poly wants to merge 9 commits into
mainfrom
feat/directional-support

Conversation

@adi-poly

@adi-poly adi-poly commented Oct 9, 2026 •

Copy link
Copy Markdown

Market makers holding ABOVE on a lower threshold and BELOW on a higher threshold can return collateral and retain the middle-bucket YES positions, then reverse the operation. Implements the DCR SDK scope.

Consumer workflow

Return collateral from the authenticated wallet's indexed directional holdings with no arguments:

await client.setupTradingApprovals();
const { outcomes, errors } = await client.mergeAllUserPositionsDirectional();
console.table(outcomes.map(outcome => ({ transactionHash: outcome.transactionHash })));
console.log(errors);

The helper repeatedly discovers pairs, chooses the largest collateral return, confirms one merge, and rediscovers until no candidate remains. Overlapping inventory is refreshed after each transaction.

To spend available pUSD and indexed middle-bucket YES holdings to restore threshold pairs:

await client.setupTradingApprovals();
const { outcomes, errors } = await client.splitAllUserPositionsDirectional();

Split-all independently selects the largest feasible amount, preferring the widest contiguous bucket range on ties, confirms the split, then rediscovers until no eligible inventory or collateral remains. It selects the best range without enumerating all subranges. It does not replay a previous merge history.

Both bulk methods return Promise<DirectionalBulkResult> containing exactly { outcomes, errors }, and wait for confirmation internally. Outcomes contain confirmed transactions. Errors retain the original failure, phase, pair/amount for execution failures, and known transaction identifiers.

  • Transient discovery, read-only preparation, and confirmation failures receive up to three operation attempts with backoff. Confirmation retries reuse the same handle; transactions are never resubmitted to recover from uncertain outcomes.
  • Definite preparation/submission rejections and confirmed transaction failures are recorded; that exact pair is excluded and processing continues, including smaller overlapping split ranges. Runs are capped at 25 failed pairs.
  • Persistent discovery failures, cancelled signing, or unresolved submission/confirmation return the accumulated summary. Uncertain transactions pause new submissions to avoid duplicating a pending operation.

The helpers use existing approvals and execute sequentially; previously confirmed transactions persist. Both depend on indexed holdings and make greedy selections rather than guaranteeing a global optimum. Inspect errors before assuming a run exhausted all eligible inventory.

Discover eligible bucket YES holdings before deciding to split:

const buckets = await client.findDirectionalBuckets();
console.table(buckets);
const bucket = buckets[0];
if (bucket) {
  console.log('pUSD needed:', bucket.requiredPusd);
  await (await client.splitDirectional(
    bucket.abovePositionId, bucket.belowPositionId, bucket.balance,
  )).wait();
}

findDirectionalBuckets({ eventId? }, options?) returns positive middle-bucket YES holdings with positionId, eventId, bucketIndex, fresh balance, requiredPusd, and adjacent threshold IDs. It refreshes bucket balances without reading or filtering by wallet pUSD. requiredPusd equals the full reported bucket balance in six-decimal base units; execution checks actual collateral. Outer buckets, NO positions, and Void are excluded. Contiguous buckets may be split together, so singleton requirements should not be summed as an optimal combined cost.

The single-pair methods mergeDirectional(positionIdA, positionIdB, amount?) and splitDirectional(positionIdA, positionIdB, amount?) accept the two threshold position IDs in either order. The SDK validates the pair and derives its event and line indices locally; callers need no event ID or line indices. Matching prepare* methods support custom signing.

amount is a bigint in six-decimal base units or 'max'; omission defaults to 'max'. Merge uses the fresh minimum of both wing balances. Split uses the fresh minimum of pUSD and every required middle-bucket YES balance. To reverse a known merge, use the same two IDs and its explicit amount:

const reverse = await client.splitDirectional(positionIdB, positionIdA, 1_000_000n);
await reverse.wait();

Discovery requires indexed holdings and refreshes candidate balances on chain. findDirectionalPairs() ranks candidates by collateral returned. Explicit known-pair execution does not require market metadata discovery.

Included behavior

  • Preserve transaction wait() handles and EOA / Deposit Wallet / Safe / Proxy execution paths.
  • Add offline decodePositionId, bucket/threshold derivation, and read-only pair discovery.
  • Support directional condition routing for split/merge/redeem, Void-inclusive event baskets, threshold composition/decomposition, and bucket NO conversion.
  • Model all four directional activity types, separating collateral movements from threshold inventory conversions and tolerating missing metadata.

The integration guide covers wallet setup, data dependencies, and staging acceptance. Integration into planCollateralReturn / executeCollateralReturnPlan remains deferred (P2).

Validation

Build, lint, typecheck, and all 1,534 local tests pass: 427 bindings tests and 1,107 client/type tests. The offline directional example runs successfully. Public type tests cover the positional signatures; live pre-write checks reject incompatible/malformed IDs and invalid amounts.

Bulk workflow tests cover bounded discovery/preparation retries, same-handle confirmation retries, retained outcomes, exact failed-pair exclusions and overlapping alternatives, uncertain submission pauses, cancellation, and the failed-pair cap. Split selection is compared with exhaustive references and a 5,000-bucket ladder. A viem receipt-boundary regression confirms that mined reverts remain TransactionFailedError while RPC failures remain TransportError. Generated declarations expose { outcomes, errors }. Live bulk execution remains unverified: the configured staging Data API hostname did not resolve during the last connectivity check.

The earlier event/line API completed a Deposit Wallet merge/reverse round trip on staging with restored inventory. The positional API rerun passed invalid-input checks but stopped during the first inventory-seeding split: the staging relayer returned context deadline exceeded, before the directional merge. Subsequent reads found zero in all six tracked position balances. The refactored live round trip remains unverified. Safe and EOA cases skip without their credentials.

The configured staging /v2/activity and /v2/positions routes returned 404, so activity, portfolio visibility, and the discovery loop remain unverified in that environment. Line-split inventory setup does not establish trading/fill settlement.

Remaining launch checks include the trade → on-chain settlement → discovery → merge → reverse flow, all wallet types, normal line-book activation, selector sponsorship/withdrawal policy, bucket portfolio visibility, and upstream PnL policy.


Note

High Risk
Introduces extensive on-chain transaction paths and ID parsing for directional collateral flows without full staging verification, so balance or encoding mistakes could misroute user funds.

Overview
feat(client): directional collateral return

Adds a full directional protocol surface: a new @polymarket/bindings/protocol export with ID schemas, decoders, and derivation helpers; updates activity, resolution, condition validation, and balance analytics to understand the new module-4 identifiers; extends Router ABI wiring and position context routing so splits/merges/redeems work on directional conditions without listings.

Client gains compose/decompose, event basket, convert, mergeDirectional/splitDirectional, wallet discovery helpers, greedy mergeAllUserPositionsDirectional / splitAllUserPositionsDirectional, shared basket amount resolution, gasless/Eoa workflows, exhaustive tests, and a directional:positions example plus the docs/directional-markets.md guide.

Beyond scope, note that end-to-end staging verification remains outstanding.

Risk

High – large surface area touching fund-moving workflows, new bulk execution loops, and protocol ID parsing; live staging coverage is incomplete so regressions could impact collateral movement.

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

@adi-poly
adi-poly marked this pull request as draft October 9, 2026 18:03

@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.

Stale Bugbot comment from a previous run.

Comment thread packages/client/src/protocol.ts Resolved
@adi-poly
adi-poly force-pushed the feat/directional-support branch 3 times, most recently from 72c917a to 51275bf Compare October 14, 2026 16:35
Add protocol ID bindings for neg-risk and directional events, buckets,
Void, and thresholds under @polymarket/bindings/protocol.

Add event split/merge, NO conversion, threshold compose/decompose, and
two-line directional split/merge as wallet actions, plus local bucket and
threshold position derivation helpers.

Native V2 condition IDs in split, merge, and redeem now resolve locally
without a market listing. Data condition filters accept directional
conditions, and resolution types include ATOMIC_DIRECTIONAL and
INCREMENTAL_DIRECTIONAL.
@adi-poly
adi-poly force-pushed the feat/directional-support branch from 51275bf to b13d690 Compare October 14, 2026 18:06
@adi-poly

Copy link
Copy Markdown
Author

bugbot run

@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.

Stale Bugbot comment from a previous run.

@adi-poly
adi-poly marked this pull request as ready for review October 14, 2026 18:31
@kartojal kartojal changed the title feat(client): support directional positions feat(client): support directional collateral return Nov 2, 2026

@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.

Stale Bugbot comment from a previous run.

@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 b4b29c1. Configure here.

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