Skip to content

feat: gated-pyth strategy variant#8

Merged
hardyjosh merged 1 commit into
mainfrom
2026-04-14-gated-pyth
Apr 15, 2026
Merged

feat: gated-pyth strategy variant#8
hardyjosh merged 1 commit into
mainfrom
2026-04-14-gated-pyth

Conversation

@hardyjosh

@hardyjosh hardyjosh commented Apr 14, 2026

Copy link
Copy Markdown
Contributor

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.rain variant of the fixed-spread pyth strategy. Reuses the same pyth-priced, multiplier-spread IO calculation but adds four SignedContext assertions in calculate-io:

  • signer<0>() must equal the api-signer binding (the trusted EOA whose key the off-chain API holds).
  • signed-context<0 0>() must equal order-counterparty() — the signature is bound to the specific taker.
  • signed-context<0 1>() must equal order-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> carries keccak256(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:

Checks

By submitting this for review, I'm confirming I've done the following:

  • added comprehensive test coverage for any changes in logic
  • made this PR as small as possible
  • linked any relevant issues or PRs
  • included screenshots (if this involves a change to a front-end/dashboard)

Summary by CodeRabbit

New Features

  • Introduced a Pyth oracle-based strategy with API-authenticated execution
  • Enables configurable order pricing using Pyth price feeds
  • Supports both standard and inverse price modes
  • Includes adjustable parameters for price multipliers and oracle timeouts

@coderabbitai

coderabbitai Bot commented Apr 14, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR adds a new gated-pyth Rain strategy to the registry and introduces its implementation file, which defines a configuration-driven orderbook with Pyth-based pricing and API-gated order execution using signed context validation.

Changes

Cohort / File(s) Summary
Registry Entry
Registry file
Added gated-pyth mapping to src/gated-pyth.rain in referenced revision, appending content with proper newline termination.
Gated Pyth Strategy
src/gated-pyth.rain
New Rain strategy definition for Base with two scenarios (standard and inverse) using Pyth oracle feeds for pricing. Includes API-gating validation via SignedContextV1 checks (signer, context recipient, order hash, expiry), baseline functions (pyth-price-baseline, pyth-price-baseline-inv), IO calculation with multiplier, and two corresponding deployments. Handle sections stubbed as no-ops.

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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Poem

🐰 A gated path through Pyth's grand price,
Where signatures seal the oracle's advice,
Base-born orders dance in scenarios two,
API-guarded treasures, verified true!
Multipliers bloom where baselines flow,
hop hop another strategy's aglow! 🌟

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: gated-pyth strategy variant' clearly and concisely summarizes the main change: adding a new gated-pyth Rain strategy variant with API-gated execution via SignedContextV1.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 2026-04-14-gated-pyth

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

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

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 14, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

1 similar comment
@coderabbitai

coderabbitai Bot commented Apr 14, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

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-pyth and base-gated-pyth-inv repeat the same field definitions, token selectors, and long pyth-pair preset 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 :ensure so 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

📥 Commits

Reviewing files that changed from the base of the PR and between 7e7993d and c3c4c42.

📒 Files selected for processing (2)
  • registry
  • src/gated-pyth.rain

Comment thread registry
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

@coderabbitai coderabbitai Bot Apr 14, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@hardyjosh

Copy link
Copy Markdown
Contributor Author

Addressing the two nitpicks from @coderabbitai:

Schema duplication between sell/buy variants — the existing fixed-spread.rain (which this strategy forks) has the same duplication pattern, as do the other multi-deployment strategies in src/. Refactoring just gated-pyth to use YAML anchors would make it inconsistent with the rest of the repo; happy to land a repo-wide refactor as a separate PR if there's appetite.

Negative test coverage for the :ensure gates — this repo doesn't currently carry test harnesses for strategy rainlang (check src/fixed-spread.rain, src/dynamic-spread.rain, src/claims.rain — none have tests here). Strategy-level assertions are exercised via integration tests against deployed orders in the raindex repo. We can add a harness pattern here as a separate initiative, but out of scope for this PR.

@coderabbitai

coderabbitai Bot commented Apr 14, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@hardyjosh Thanks for the context — both points make sense.

On schema duplication: keeping gated-pyth.rain consistent with the existing multi-deployment strategy pattern is the right call. A repo-wide YAML anchor refactor as a separate PR sounds like a good future initiative if there's appetite.

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
Learnt from: hardyjosh
URL: https://github.com/ST0x-Technology/st0x.registry/pull/8

Timestamp: 2026-04-14T17:28:29.620Z
Learning: In the ST0x-Technology/st0x.registry repository, schema duplication between sell/buy (standard/inverse) deployment variants is an intentional, repo-wide pattern shared by all multi-deployment strategies (e.g., fixed-spread.rain, dynamic-spread.rain, gated-pyth.rain). Do not flag this as a refactoring issue in individual PRs; a repo-wide YAML anchor refactor would be the appropriate approach as a dedicated PR.

Learnt from: hardyjosh
URL: https://github.com/ST0x-Technology/st0x.registry/pull/8

Timestamp: 2026-04-14T17:28:29.620Z
Learning: In the ST0x-Technology/st0x.registry repository, strategy rainlang files (e.g., fixed-spread.rain, dynamic-spread.rain, claims.rain, gated-pyth.rain) do not carry inline test harnesses. Strategy-level assertions (including :ensure gates) are exercised via integration tests against deployed orders in the raindex repo. Do not flag missing unit/negative tests for :ensure gates in this repository.

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.

@hardyjosh hardyjosh marked this pull request as ready for review April 14, 2026 17:28
@hardyjosh hardyjosh merged commit 634bfd0 into main Apr 15, 2026
2 checks passed
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