Skip to main content
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

[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

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

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:
[resolver] tick: 12 expired tuples, 0 already-tried, 12 to try
[resolver] redeem succeeded oracle=0x… strike=$65000 isUp=true digest=0x…

Files

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.