@darkpool/resolver package runs two jobs in one Express process:
- OO settlement endpoint. HTTP service for atomic Pyth + redeem PTBs (used by the frontend when a market is ready to settle).
- Auto-settle keeper. Polymarket-style daemon that scans
predict::PositionMintedevents, groups by(oracle, manager, strike, isUp), and submits per-positionpredict::redeem_permissionlessPTBs once an oracle expires.
:8082. Configurable via RESOLVER_PORT.
Startup log
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
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
Auto-settle keeper
File:packages/resolver/src/autoSettleTick.ts. Runs every 60s.
Algorithm:
- Page recent
predict::PositionMintedevents (up to 1000). - Aggregate by
(oracle, manager, strike, isUp). Unique positions. - For each oracle: check
expiry_ms,active, andsettlement_price. - If expired and not yet settled: submit one
predict::redeem_permissionlessper PTB (one position per tx so a single abort can’t sink the batch). - Memoise tried tuples. Successes never re-fire; failures back off 30 min.
- Always self-pays. Enoki allow-list rejects Predict’s Move targets.
Files
Gotcha: don’t run two resolvers
Atsx 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.