Skip to content

Pyth pro migration: dual-accept mint, Hermes-free SDKs, v2 crank - #1207

Open
bryzettler wants to merge 36 commits into
developfrom
feat/pyth-upgrade
Open

Pyth pro migration: dual-accept mint, Hermes-free SDKs, v2 crank#1207
bryzettler wants to merge 36 commits into
developfrom
feat/pyth-upgrade

Conversation

@bryzettler

@bryzettler bryzettler commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Pyth Core upgrade — early migration (cutover 2026-07-31)

Migrates every Helium surface off Hermes ahead of Pyth's July 31 cutover (paid API keys + new router trust model). Mainnet rollout is sequenced separately: a 48h feed-freshness gate on the v2 crank, then Squads-executed program upgrades and AutoTopOff flips; merging this PR only triggers changesets and devnet deploys.

On-chain programs

  • data-credits — dual-accept: mint_data_credits_v0 accepts an hnt_price_oracle owned by either the legacy receiver (rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ) or the pro receiver. The check keys on the oracle account's owner program, not a pinned address — this is what keeps the live ephemeral-update path working (see below).
  • data-credits — has_one pin (6b69a7d, ⚠️ deploy-gated): re-pins hnt_price_oracle via has_one against a stored field that is now updatable through update_data_credits_v0 (new hntPriceOracle arg + resolver default). This commit must NOT ship in the initial program-data-credits-0.2.7 deploy — it breaks every ephemeral minter the moment it hits mainnet. The 0.2.7 tag is cut from a release branch at 187d7ba (last commit before it); the has_one code ships later as 0.2.8 only after ephemeral traffic is confirmed dead.
  • dc-auto-top: HNT feed constant flipped to the crank-fed pro feed He5mh… and crank URL to the tuktuk-pyth-v2 hostname.
  • tuktuk-dca: fixtures re-pinned to the pro receiver (SDK repin only — zero live accounts on mainnet).
  • Vendored pyth_solana_receiver_sdk: repinned to pro program IDs; legacy IDs kept as LEGACY_* constants (used by dual-accept).

The ephemeral price-update path (why dual-accept is shaped this way)

Live mint traffic today (~5 mints/14h, 800-tx scan of the DataCredits PDA on 2026-07-17) does not read the crank-maintained legacy feed 4Ddm…. Every observed mint posts its own ephemeral Hermes price update in-transaction via the legacy receiver rec5… — the current published-SDK / blockchain-api behavior: fetch a VAA from Hermes, post it to a keypair-addressed ephemeral account, mint against it, close it. Consequences baked into this PR:

  • Dual-accept's legacy arm validates by owner program, so ephemeral accounts (which have arbitrary addresses) keep working through go-live with zero client coordination.
  • These minters don't depend on the legacy crank, so the post-cutover legacy crank teardown doesn't break them.
  • They are broken by the has_one pin, which is why that deploy is gated on a stricter traffic check: no mint_data_credits_v0 with an oracle owned by rec5… at all. That check fails as of 2026-07-17 (all observed mints go through rec5…); earliest realistic window is mid-August, after prod blockchain-api ships the Hermes-free SDK and stragglers upgrade.
  • When 0.2.8 does deploy, the program upgrade and updateDataCreditsV0 { hntPriceOracle: He5mh… } must execute back-to-back in the same Squads session — between them, mints are pinned to the stored 4Ddm…, stale once the legacy crank is torn down.

Services & monitoring

  • tuktuk-pyth-service: receiver / push-oracle / wormhole program IDs, Hermes URL, and API key are now env-configurable (defaults = legacy), enabling the parallel tuktuk-pyth-v2 deployment against the pro programs. v2 encoded-VAA PDA namespaced with a "v2" seed to avoid collision with the v1 deployment. Pre-flight hardening from the freshness-gate investigation: stale VAA reads re-poll before memo-bail, wrong-status VAAs defer to turner simulation, and the poll window extends past observed RPC read lag.
  • monitor-service: new gauges for the v2 crank + payer balances, the legacy crank balance (removed once the has_one train ships), and solana_pyth_publish_time feed freshness. Backing Grafana/Mimir alert rules (crank balance thresholds, feed-stale >600s) land via helium-terraform.

Client SDKs (breaking)

  • @helium/data-credits-sdk / @helium/currency-utils: Hermes dropped entirely. mintDataCredits passes the crank-fed HNT feed account; getOraclePrice reads it on-chain. Breaking: priceUpdates removed from the return value, PYTH_HERMES_URL exports deleted; {txs} shape unchanged, zero Pyth configuration needed by consumers.
  • data-credits-sdk resolvers: default hntPriceOracle to the pro feed so old call sites keep building.
  • LUT scripts: create-common-lut.ts / create-tuktuk-dca-lut.ts corrected to the pro feed so a future re-run doesn't bake the dead feed back in. No on-chain LUT work — the live mainnet LUT never contained a Pyth feed, HELIUM_COMMON_LUT/_DEVNET unchanged.

Release notes

  • Changesets pending for @helium/spl-utils, @helium/currency-utils, @helium/data-credits-sdk (minor). Merging this PR into develop triggers the Version-Packages PR and devnet program deploys — mainnet program deploys are tag-driven and gated on the 48h freshness gate + Squads session.
  • blockchain-api e2e mint test stays it.skipped until dual-accept is live on mainnet (surfpool clones the mainnet program); it gets un-skipped as part of the post-flip release train before this merge finalizes.

Receiver and push-oracle IDs now point at the pro deployments (rec2/pyt2),
matching upstream's pro-compatible feature. Legacy IDs stay exported as
LEGACY_* constants for the transition-window dual-accept checks (ticket 03).
Also fixes the crate's unit tests, broken since the Anchor 0.31 bump
(discriminator() -> DISCRIMINATOR const).
Receiver, push-oracle, and wormhole program IDs plus optional Hermes API
key (Authorization: Bearer) become env config with legacy defaults, so the
existing deployment runs unchanged and a pro deployment needs only env.
Note: prettier reformatted index.ts on first touch; semantic changes are
the env wiring only.
Extract makePythReceiver so both call sites share the program-ID wiring,
rename convertedIDL to wormholeIdl, and stop duplicating the legacy
default IDs in the README (src/env.ts is the single source).
Repin-only change: the vendored Pyth receiver SDK (issue 01) already flips the DCA owner checks to the pro receiver (rec2). Re-fixture the integration suite to clone the pro-receiver-owned USDC/USD and HNT/USD PriceUpdateV2 accounts so the create->repay round trip runs against pro feeds.
After the Pyth Core upgrade puts Hermes behind paid API keys, third
parties and blockchain-api keep minting DC and reading HNT prices with a
plain package bump and zero Pyth configuration (ADR-0002).

- mintDataCredits builds a single transaction that references the
  crank-fed pro HNT feed account as the price oracle; the ephemeral
  Hermes price-update posting path is removed. The txs: [{tx, signers}]
  return shape is preserved; the priceUpdates return field is removed.
- getOraclePrice reads and parses the pro feed account over its existing
  Solana connection (no Hermes), preserving its return shape.
- PYTH_HERMES_URL exports removed from data-credits-sdk and
  currency-utils; hermes-client dependency dropped.
- HNT feed-account constant flipped to the pro-receiver-owned account;
  the feed-id constant is untouched.
- blockchain-api mint procedure: Squads comment rewritten (minting stays
  non-proposable as a product decision, not the stale-by-execution
  technical reason, which no longer holds).

Integration test tests/data-credits.ts passes (8 passing, built with
TESTING=true as CI does).

The blockchain-api e2e mint test is upgraded to real on-chain submission
but marked it.skip until ticket 09 deploys the dual-accept data-credits
program to mainnet: surfpool clones the un-upgraded mainnet program,
which rejects the pro-receiver-owned feed with AccountOwnedByWrongProgram.
@bryzettler
bryzettler marked this pull request as ready for review July 16, 2026 15:19
@madninja

Copy link
Copy Markdown
Member

Reminder that this patches the pyth feed/receiver which will need to happen for #1197 as well

Observed >10s account-read lag on the service RPC defeats any bounded
poll: odd ticks read the pre-init Verified status for the full 10s window
and memo-bailed, halving the write cadence. The turner simulates against
its own RPC, which sees the same-slot init within ~1s — so serve the
writes on a status mismatch and let simulation arbitrate. The memo bail
remains only for a missing account, which a stale read can't fake once
polling covers the same-transaction create.
@bryzettler bryzettler changed the title Feat/pyth upgrade Pyth pro migration: dual-accept mint, Hermes-free SDKs, v2 crank Jul 17, 2026
@bryzettler
bryzettler requested a review from madninja July 18, 2026 01:55
Each of the 17 e2e matrix jobs ran a full build-anchor. When the anchor
build cache was evicted (repo sits at the 10GB Actions cache cap), every
job did a cold anchor build that can't finish inside the 20-minute job
timeout, so entire runs died as 'cancelled'. The matrix jobs only need
target/types to build @helium/idls; build once in a dedicated job with a
60-minute budget and hand the generated types/IDLs to the matrix via an
artifact.
…hange

Build the changed programs' IDLs at the PR base and head with
anchor idl build and require an @helium/idls changeset only when the
generated IDL actually differs (ignoring metadata.version). Internal
logic changes like the SIMD-553 CU optimizations no longer demand a
no-op package bump. Verified locally: all four programs this branch
touches produce byte-identical IDLs vs develop.
…tion

The websocket path can reject with the raw confirmation result
({ err: { InstructionError } }) rather than the InstructionError
itself, which #1213's fix didn't cover.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants