> ## Documentation Index
> Fetch the complete documentation index at: https://docs.darkpool.fun/llms.txt
> Use this file to discover all available pages before exploring further.

# Resolver

> OO settlement endpoint + auto-settle keeper (Express on :8082).

The `@darkpool/resolver` package runs two jobs in one Express process:

1. **OO settlement endpoint.** HTTP service for atomic Pyth + redeem PTBs (used by the frontend when a market is ready to settle).
2. **Auto-settle keeper.** Polymarket-style daemon that scans `predict::PositionMinted` events, groups by `(oracle, manager, strike, isUp)`, and submits per-position `predict::redeem_permissionless` PTBs once an oracle expires.

Port: `:8082`. Configurable via `RESOLVER_PORT`.

## Startup log

```text theme={"system"}
[resolver] RESOLVER_KEY loaded. Signing as 0x…
DarkPool resolver on :8082
```

If you see `RESOLVER_KEY is not set`, the keeper runs in unsigned mode and never settles. Fix by ensuring `.env` lives at repo root and the value is a single-line `suiprivkey1…` string (no quotes).

## Atomic settlement PTB

<Frame caption="One PTB pulls a Hermes VAA, pushes Pyth, settles OracleSVI, then redeems each opted-in position and emits attribution. Per-position scoping isolates aborts.">
  <img src="https://mintcdn.com/dp-550c5f99/aCprxKEdnuYXHyw4/images/resolver-flow.png?fit=max&auto=format&n=aCprxKEdnuYXHyw4&q=85&s=0d3dc35f0f83697f5eac8ca4590a33b7" alt="Oracle to auto-settle to permissionless redeem flow. Step 1: Pull Hermes VAA via the Pyth Sui SDK. Step 2: pyth::update_price_feeds consumes the hot-potato. Step 3: OracleSVI auto-settles inside Predict. Step 4: market_key::new or range_key::new per redeemer. Step 5: predict::redeem_permissionless per redeemer, which credits the manager. Step 6: predict_facade::attribute_redeem emits an event." width="1024" height="1536" data-path="images/resolver-flow.png" />
</Frame>

If `ENOKI_PRIVATE_KEY` is set, the PTB is sponsored, user-perceived gas is zero. Falls back to the resolver's own keypair paying gas if Enoki throws.

## Routes

```http theme={"system"}
GET  /health                          -> { network, sponsorMode, keeperSigner }
POST /markets/:id/resolve
  body: {
    oracleSviObjectId: string,
    optedInRedeemers: [{ predictManagerId, marketKind }],
    pythFeedIdHex?: string,
    pythPriceInfoObjectId?: string,
  }
  -> { digest } | { ptbBytes }    # depending on sponsor / RESOLVER_KEY config
```

## Auto-settle keeper

File: `packages/resolver/src/autoSettleTick.ts`. Runs every 60s.

Algorithm:

1. Page recent `predict::PositionMinted` events (up to 1000).
2. Aggregate by `(oracle, manager, strike, isUp)`. Unique positions.
3. For each oracle: check `expiry_ms`, `active`, and `settlement_price`.
4. If expired and not yet settled: submit **one** `predict::redeem_permissionless` per PTB (one position per tx so a single abort can't sink the batch).
5. Memoise tried tuples. Successes never re-fire; failures back off 30 min.
6. **Always self-pays.** Enoki allow-list rejects Predict's Move targets.

Log excerpt:

```text theme={"system"}
[resolver] tick: 12 expired tuples, 0 already-tried, 12 to try
[resolver] redeem succeeded oracle=0x… strike=$65000 isUp=true digest=0x…
```

## Files

```text theme={"system"}
resolver/
├── package.json                           # Express + Pyth Sui SDK + Enoki
├── tsconfig.json
└── src/
    ├── index.ts                           # Express server on :8082
    ├── buildAtomicSettlementPTB.ts        # Pyth pull + OracleSVI settle + redeem
    ├── enokiSponsor.ts                    # Sponsored-tx wrapper with self-pay fallback
    └── autoSettleTick.ts                  # Keeper loop
```

## Gotcha: don't run two resolvers

A `tsx watch` reload can leave the old `setInterval` alive while a new one starts. You'll see duplicate-digest log lines and validator sequence-number conflicts. Fix: `⌃C` every resolver shell, `pkill -f "resolver/src/index"`, restart one.
