feat: gated-pyth strategy variant#8
Conversation
📝 WalkthroughWalkthroughThis PR adds a new Changes
Sequence Diagram(s)sequenceDiagram
participant User as User/API
participant Orderbook as Orderbook
participant SignedContext as SignedContextV1
participant Pyth as Pyth Oracle
participant Handler as Order Handler
User->>Orderbook: Submit signed order
Orderbook->>SignedContext: Verify API signer match
alt Signer mismatch
SignedContext-->>Orderbook: Validation failed
Orderbook-->>User: Revert
else Signer valid
SignedContext->>SignedContext: Check recipient = counterparty
SignedContext->>SignedContext: Check order hash match
SignedContext->>SignedContext: Check expiry not in past
alt Any check fails
SignedContext-->>Orderbook: Validation failed
Orderbook-->>User: Revert
else All validations pass
Orderbook->>Pyth: Request price for pyth-pair
Pyth-->>Orderbook: Return price
Orderbook->>Orderbook: Compute baseline = pyth-price(pair, timeout)
Orderbook->>Orderbook: Calculate IO = baseline × multiplier
Orderbook->>Handler: Execute order with computed IO
Handler-->>User: Order accepted
end
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~30 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsGit: Failed to clone repository. Please run the Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Pyth-priced fixed-spread variant gated to API-signed callers. The
`calculate-io` entrypoint asserts a SignedContextV1 at index 0 produced
by a trusted `api-signer`, authorising the exact counterparty, order
hash and a not-yet-expired deadline. Intended for API-reserved USDC
liquidity that shouldn't be drained by arb bots on the public book.
Signed context layout:
<0 0>: recipient -- must match order-counterparty()
<0 1>: order hash -- must match order-hash()
<0 2>: expiry timestamp -- must be >= now()
<0 3>: api-key id -- keccak256(api key id), signed for
off-chain attribution only.
The registry hash in the pointer file is stale for the new entry; will
be refreshed by a follow-up "Update registry hash" commit post-merge.
7e0101e to
c3c4c42
Compare
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
1 similar comment
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/gated-pyth.rain (2)
54-141: Factor the shared deployment schema if this format supports reuse.
base-gated-pythandbase-gated-pyth-invrepeat the same field definitions, token selectors, and longpyth-pairpreset list. That makes feed or copy updates easy to miss in one half. If this config format supports anchors/includes, extract the common block and override only the deployment-specific copy.Also applies to: 145-230
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/gated-pyth.rain` around lines 54 - 141, The deployment schemas for base-gated-pyth and base-gated-pyth-inv duplicate the same fields (notably the pyth-pair presets, fields: binding entries like "api-signer", "pyth-pair", "baseline-multiplier", "oracle-price-timeout", and select-tokens keys "output"/"input") — extract that repeated block into a single shared schema (e.g., common-fields or pyth-presets anchor/include) and have base-gated-pyth and base-gated-pyth-inv reference that shared block, overriding only deployment-specific items (deposit token order or any differing defaults) so the long pyth-pair preset list and shared field definitions live in one place and are not duplicated.
253-283: Add negative coverage for the new auth gate.This is the only access-control path in the strategy, but the PR does not add any scenario exercising signer mismatch, wrong counterparty, wrong order hash, or expired signatures. Please add one happy path plus one failing case per
:ensureso future changes cannot silently open or brick the order.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/gated-pyth.rain` around lines 253 - 283, Add a test harness exercising the new API gating: implement one positive (happy) test where signer<0>() equals api-signer, signed-context<0 0>() equals order-counterparty(), signed-context<0 1>() equals order-hash(), and signed-context<0 2>() >= now() to confirm calculate-io/offer-amount/io succeed, plus four negative tests each breaking a single :ensure (signer mismatch for signer<0>() vs api-signer, wrong recipient via signed-context<0 0>(), wrong order hash via signed-context<0 1>(), and expired signature by making signed-context<0 2>() < now()) and assert the corresponding error messages ("Untrusted API signer", "Wrong recipient", "Wrong order hash", "Authorization expired") are thrown; reference these symbols (signer<0>(), api-signer, signed-context<0 0>(), signed-context<0 1>(), signed-context<0 2>(), order-counterparty(), order-hash(), now(), calculate-io/io/offer-amount) when wiring inputs so each test isolates a single failure mode.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@registry`:
- Line 9: The registry entry for gated-pyth points at commit
ac4594e349b09b687cf13b249cb97754e07bfbd6 while src/gated-pyth.rain only exists
on this branch; update the registry pointer by replacing the commit hash with
the upstream commit that actually contains src/gated-pyth.rain (or remove/defer
adding the gated-pyth line until that upstream commit is published) so consumers
resolving gated-pyth succeed; look for the line containing "gated-pyth
https://raw.githubusercontent.com/.../src/gated-pyth.rain" and update the hash
or remove the entry accordingly.
---
Nitpick comments:
In `@src/gated-pyth.rain`:
- Around line 54-141: The deployment schemas for base-gated-pyth and
base-gated-pyth-inv duplicate the same fields (notably the pyth-pair presets,
fields: binding entries like "api-signer", "pyth-pair", "baseline-multiplier",
"oracle-price-timeout", and select-tokens keys "output"/"input") — extract that
repeated block into a single shared schema (e.g., common-fields or pyth-presets
anchor/include) and have base-gated-pyth and base-gated-pyth-inv reference that
shared block, overriding only deployment-specific items (deposit token order or
any differing defaults) so the long pyth-pair preset list and shared field
definitions live in one place and are not duplicated.
- Around line 253-283: Add a test harness exercising the new API gating:
implement one positive (happy) test where signer<0>() equals api-signer,
signed-context<0 0>() equals order-counterparty(), signed-context<0 1>() equals
order-hash(), and signed-context<0 2>() >= now() to confirm
calculate-io/offer-amount/io succeed, plus four negative tests each breaking a
single :ensure (signer mismatch for signer<0>() vs api-signer, wrong recipient
via signed-context<0 0>(), wrong order hash via signed-context<0 1>(), and
expired signature by making signed-context<0 2>() < now()) and assert the
corresponding error messages ("Untrusted API signer", "Wrong recipient", "Wrong
order hash", "Authorization expired") are thrown; reference these symbols
(signer<0>(), api-signer, signed-context<0 0>(), signed-context<0 1>(),
signed-context<0 2>(), order-counterparty(), order-hash(), now(),
calculate-io/io/offer-amount) when wiring inputs so each test isolates a single
failure mode.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 0c87ac4a-22ee-4a1c-84c7-2367c88433dd
📒 Files selected for processing (2)
registrysrc/gated-pyth.rain
| canary https://raw.githubusercontent.com/ST0x-Technology/st0x.registry/ac4594e349b09b687cf13b249cb97754e07bfbd6/src/canary.rain | ||
| claims https://raw.githubusercontent.com/ST0x-Technology/st0x.registry/ac4594e349b09b687cf13b249cb97754e07bfbd6/src/claims.rain | ||
| fixed-spread https://raw.githubusercontent.com/ST0x-Technology/st0x.registry/ac4594e349b09b687cf13b249cb97754e07bfbd6/src/fixed-spread.rain | ||
| gated-pyth https://raw.githubusercontent.com/ST0x-Technology/st0x.registry/ac4594e349b09b687cf13b249cb97754e07bfbd6/src/gated-pyth.rain |
There was a problem hiding this comment.
Update the registry pointer before merge.
This entry still targets ac4594e349b09b687cf13b249cb97754e07bfbd6, but src/gated-pyth.rain only exists on this branch. Consumers resolving gated-pyth from the registry will fail until the hash refresh lands. Either update the hash in this PR before merge or defer the new entry until the upstream commit exists.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@registry` at line 9, The registry entry for gated-pyth points at commit
ac4594e349b09b687cf13b249cb97754e07bfbd6 while src/gated-pyth.rain only exists
on this branch; update the registry pointer by replacing the commit hash with
the upstream commit that actually contains src/gated-pyth.rain (or remove/defer
adding the gated-pyth line until that upstream commit is published) so consumers
resolving gated-pyth succeed; look for the line containing "gated-pyth
https://raw.githubusercontent.com/.../src/gated-pyth.rain" and update the hash
or remove the entry accordingly.
There was a problem hiding this comment.
This matches the established convention in this repo — new strategy entries are added alongside the existing stale commit hash, and a subsequent Update registry hash commit refreshes all entries in one pass after merge (see e.g. 3d9e5e7). The PR body explicitly notes this as a follow-up step. Happy to revisit if you want to change the convention, but keeping the existing flow for this PR.
There was a problem hiding this comment.
@hardyjosh, thanks for the context — that makes sense. The two-step pattern (add entry with current hash → refresh all hashes in a follow-up commit) is a perfectly reasonable workflow, and since it's already documented in the PR body I'm happy to drop this concern.
✏️ Learnings added
Learnt from: hardyjosh
Repo: ST0x-Technology/st0x.registry PR: 8
File: registry:9-9
Timestamp: 2026-04-14T17:28:14.673Z
Learning: In the ST0x-Technology/st0x.registry repository, the established convention for adding new strategy entries to the `registry` file is to add the new entry pointing at the current (stale) commit hash alongside all other entries, and then land a separate follow-up commit titled "Update registry hash" that refreshes all entry hashes in one pass after the PR is merged. New entries therefore intentionally reference a hash that does not yet contain the new file at the time the PR is opened. This is not a bug; do not flag it as an issue in future reviews.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
There was a problem hiding this comment.
Acknowledged — the hash intentionally points at a pre-merge commit. The registry entry will be bumped to main HEAD as a follow-up commit on this PR once the upstream .rain lands on main. Noted in the cross-repo caution block too.
|
Addressing the two nitpicks from @coderabbitai: Schema duplication between sell/buy variants — the existing Negative test coverage for the |
|
Tip For best results, initiate chat on the files or code changes.
On schema duplication: keeping On test coverage: understood — strategy rainlang assertions are validated through integration tests in the raindex repo, not inline harnesses here. That's a well-established pattern for this codebase. Happy to continue the review without flagging either of these. ✏️ Learnings added
|

Motivation
The rain-solver arbs between our Raindex book and pools we run on Hyperliquid/other venues, which quickly drains the shared USDC buy-side liquidity on public pyth-priced orders. A single arb can burn $10k of buy-side liquidity in one trade, leaving API users of st0x.io with no available liquidity even though they only need a $1k fill.
We want a separate pot of USDC that's only reachable through the API, so arb flow can't touch it. There's no native way to gate a vault, so the gate needs to live in the strategy itself.
Solution
New
gated-pyth.rainvariant of the fixed-spread pyth strategy. Reuses the same pyth-priced, multiplier-spread IO calculation but adds four SignedContext assertions incalculate-io:signer<0>()must equal theapi-signerbinding (the trusted EOA whose key the off-chain API holds).signed-context<0 0>()must equalorder-counterparty()— the signature is bound to the specific taker.signed-context<0 1>()must equalorder-hash()— the signature is bound to this order.signed-context<0 2>()must be>= now()— the authorisation is not expired.A fourth entry
<0 3>carrieskeccak256(api_key_id)— signed but not asserted on-chain. It exists purely so trades can be attributed back to the calling API key off-chain via event indexing.The registry pointer file entry is added using the existing stale commit hash. A follow-up "Update registry hash" commit post-merge will refresh all entries to the new head.
Companion PRs:
SignedContextInjectorso rain.orderbook can receive caller-supplied signed contexts.SignedContextV1this strategy asserts.Checks
By submitting this for review, I'm confirming I've done the following:
Summary by CodeRabbit
New Features