{"id":51332047,"url":"https://github.com/magicblock-labs/hydra","last_synced_at":"2026-07-01T23:32:35.782Z","repository":{"id":353420898,"uuid":"1214394896","full_name":"magicblock-labs/hydra","owner":"magicblock-labs","description":"Permissionless Solana crank for scheduling instructions with minimum overhead.","archived":false,"fork":false,"pushed_at":"2026-06-26T16:38:55.000Z","size":1670,"stargazers_count":18,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-26T18:10:42.003Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/magicblock-labs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-18T14:15:52.000Z","updated_at":"2026-05-06T10:38:47.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/magicblock-labs/hydra","commit_stats":null,"previous_names":["magicblock-labs/hydra"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/magicblock-labs/hydra","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/magicblock-labs%2Fhydra","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/magicblock-labs%2Fhydra/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/magicblock-labs%2Fhydra/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/magicblock-labs%2Fhydra/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/magicblock-labs","download_url":"https://codeload.github.com/magicblock-labs/hydra/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/magicblock-labs%2Fhydra/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35027341,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-01T02:00:05.325Z","response_time":130,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-01T23:32:34.808Z","updated_at":"2026-07-01T23:32:35.773Z","avatar_url":"https://github.com/magicblock-labs.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"./logo.png\" width=\"168\" alt=\"Hydra logo\" /\u003e\n  \u003ch1\u003eHydra\u003c/h1\u003e\n  \u003cp\u003ePermissionless Solana crank for scheduling instructions with minimum overhead.\u003c/p\u003e\n\u003c/div\u003e\n\n## Packages\n\n| Package | Description | Version | Docs |\n|:--------|:------------|:--------|:-----|\n| `hydra` | Pinocchio `no_std` on-chain program | `0.1.0` | [Overview](#overview) |\n| `hydra-api` | Shared Rust types, builders, and CPI helpers | `0.1.0` | [Integrating Hydra](#integrating-hydra) |\n\n## Overview\n\nHydra stores a scheduled instruction in a crank PDA and lets anyone trigger it\nwhen the schedule is due.\n\nEach trigger transaction has two instructions:\n\n```text\nix[k]   = Hydra.Trigger\nix[k+1] = scheduled instruction\n```\n\n`Trigger` verifies `ix[k+1]` against the bytes stored in the crank account. If\nthe scheduled instruction fails, the whole transaction rolls back.\n\nKey constraints:\n\n- scheduled instructions run top-level, not via CPI\n- scheduled instructions cannot require signer metas\n- `Trigger` is top-level only\n\n## Motivation\n\nHydra is not a general-purpose automation platform. It's a minimal runner\nfor **permissionless scheduled instructions** — oracle ticks, AMM pokes,\npublic `crank()` endpoints, settle / liquidation gates that accept any\nsigner. Other schedulers (Clockwork, Tuktuk, …) dispatch via CPI from\ntheir own program; Hydra instead verifies the scheduled instruction\nagainst an on-chain template at the top level and lets the runtime\nexecute it as a sibling ix. No CPI frame, no dispatch overhead.\n\nThe cranker submits a plain two-instruction transaction; `Trigger`\n`memcmp`s `ix[k+1]` against the bytes stored on the crank PDA at\n`Create` time (~60 CU), collects the reward, and advances state.\nSolana transaction atomicity handles failure — if the scheduled\ninstruction reverts, the whole tx reverts and Hydra's payout / state\nadvance revert with it. The scheduled instruction itself runs top-level\nand gets the full CU budget and stack depth.\n\n## Compute Units\n\nMeasured with `logging` disabled:\n\n| Instruction | Hydra CU |\n|---|---:|\n| `Create` | 3292 |\n| `Trigger` | 464 |\n| `Trigger` (reject: no follow-up) | 378 |\n| `Cancel` | 128 |\n| `Close` (reject: healthy) | 139 |\n| `Close` (underfunded) | 150 |\n\nReproduce:\n\n```sh\ncargo build-sbf --manifest-path programs/hydra/Cargo.toml\ncargo build-sbf --manifest-path tests/programs/noop/Cargo.toml\ncargo test -p hydra-tests cu_table -- --ignored --nocapture\n```\n\n## Build\n\n```sh\n# Build the on-chain program.\ncargo build-sbf --manifest-path programs/hydra/Cargo.toml\n\n# Build the cranker.\ncargo build -p hydra-cranker\n\n# Run the test suite.\ncargo test -p hydra-tests\n```\n\n## Integrating Hydra\n\nUse `hydra-api` from clients or from your own on-chain program.\n\n| Use case | Feature | API |\n|---|---|---|\n| Host-side client | `client` | `Instruction` builders |\n| `solana-program` / Anchor CPI | `cpi-native` | `hydra_api::cpi::native::*` |\n| Pinocchio CPI | `cpi-pinocchio` | `hydra_api::cpi::pinocchio::*` |\n\n`Trigger` is not exposed as a CPI helper. It must be sent as a top-level\ninstruction.\n\nExamples:\n\n- `examples/native`\n- `examples/anchor`\n- `examples/pinocchio`\n\n## Creating a Crank\n\n```rust\nuse hydra_api::instruction::{self as ix, CreateArgs};\n\nlet seed = [0x42u8; 32];\nlet (crank, _bump) = ix::find_crank_pda(\u0026seed);\n\nlet create = ix::create(\n    payer_pubkey,\n    crank,\n    \u0026CreateArgs {\n        seed,\n        authority: [0u8; 32],\n        start_slot: 0,\n        interval_slots: 400,\n        remaining: 0,\n        priority_tip: 2_500,\n        cu_limit: 0, // 0 = cranker omits SetComputeUnitLimit; cap 1_400_000\n        scheduled_program_id: memo::ID,\n        scheduled_metas: \u0026[],\n        scheduled_data: b\"tick\",\n    },\n);\n```\n\n## Authenticating a Crank PDA\n\nScheduled instructions run top-level, so a target program cannot rely on Hydra\nCPI signer privileges. If the scheduled ix needs to authenticate a Hydra crank,\ninclude the crank PDA and instructions sysvar in the scheduled ix and verify\nthe sibling instructions in both directions.\n\nHydra does the forward check: `Trigger` reads the instructions sysvar and\nrequires `ix[k+1]` to byte-match the scheduled ix stored in the crank PDA. The\nscheduled program can do the reverse check: read the current instruction index,\nload `ix[k-1]`, and require it to be Hydra `Trigger` for the same crank PDA.\n\n```text\nix[k-1] = Hydra.Trigger(crank = expected_crank_pda, ...)\nix[k]   = your scheduled ix(crank = expected_crank_pda, instructions_sysvar, ...)\n```\n\nIn the scheduled program, reject unless:\n\n- `expected_crank_pda == Pubkey::find_program_address([b\"crank\", seed], hydra_id)`\n- `crank.owner == hydra_id`\n- the previous ix program id is `hydra_id`\n- the previous ix discriminator is `Trigger`\n- the previous ix first account is the same crank PDA\n\nIf the scheduled ix also needs to verify who created the schedule, read the\ncrank header, for example with `hydra_api::state::load_crank`, and check both\n`authority` and `authority_signer`. `authority` is the value supplied at\n`Create`; `authority_signer == 1` means the `Create` payer/signer was that\nsame authority. Require `authority == expected_authority` and\n`authority_signer == 1` when scheduler identity matters. If\n`authority_signer == 0`, the authority is only stored for cancellation and is\nnot proof that the authority signed the schedule creation.\n\n## Costs\n\nA crank has two upfront costs and a small per-trigger fee:\n\n| | Amount | What happens to it |\n|---|---|---|\n| **Rent deposit** | ~0.002 SOL | Locked while the crank lives, refunded on close |\n| **Create tx fee** | 5,000 lamports | Standard Solana base fee |\n| **Per trigger** | 10,000 lamports + `priority_tip` | Drawn from the crank's balance, paid to the cranker |\n\nThe rent deposit scales with the scheduled instruction's size — ~0.002 SOL\nfor a minimal ix, up to ~0.003 SOL with a handful of accounts and a bit of\ndata. **You get it back**: `Cancel` refunds 100% to the authority; `Close`\nrefunds everything minus a 10,000-lamport cleanup bounty (≈99.5 – 99.7% of\nthe deposit).\n\nFund future triggers by sending a `system_program::transfer` to the crank\nPDA — typically in the same transaction as `Create` — sized to\n`runs × (10,000 + priority_tip)`. If the crank runs out of lamports,\n`Trigger` stops firing before it can touch the rent deposit, so that\ndeposit is always recoverable.\n\n## Running the Cranker\n\nThe cranker is event-driven and uses WebSocket subscriptions for account and\nslot updates. Optionally, a Yellowstone gRPC endpoint can be wired in\nalongside the WS subs (`--grpc-url`) for redundancy and lower latency.\n\n```sh\n# Devnet\nhydra-cranker --keypair ~/.config/solana/cranker.json\n\n# Custom RPC / WebSocket endpoints\nhydra-cranker \\\n  --keypair ~/.config/solana/cranker.json \\\n  --rpc-url https://your.rpc.example \\\n  --ws-url wss://your.rpc.example\n\n# With Prometheus metrics at http://0.0.0.0:9100/metrics\nhydra-cranker \\\n  --keypair ~/.config/solana/cranker.json \\\n  --prometheus-port 9100\n\n# With a Yellowstone gRPC endpoint **in addition to** the WS subscriptions.\n# Account + slot updates flow into the same cache and slot tick channel —\n# whichever transport delivers first wins, the other is a redundant backstop.\nhydra-cranker \\\n  --keypair ~/.config/solana/cranker.json \\\n  --grpc-url https://your.grpc.example:10000 \\\n  --grpc-x-token your-optional-x-token\n```\n\n### Metrics\n\nWhen `--prometheus-port \u003cPORT\u003e` is set the cranker serves `/metrics` in\nPrometheus text format on `0.0.0.0:\u003cPORT\u003e`. All series are namespaced\n`hydra_cranker_*` and pre-initialised so `rate()` works from scrape 1.\n\n| Metric | Type | Labels | Meaning |\n|---|---|---|---|\n| `cranks_cached` | gauge | — | Cranks currently in the in-memory cache. |\n| `current_slot` | gauge | — | Last slot observed from `slotSubscribe`. |\n| `eligible_now` | gauge | — | Cranks eligible to trigger on the last slot tick. |\n| `triggers_submitted_total` | counter | `result={ok,err}` | Triggers submitted. |\n| `ws_reconnects_total` | counter | `source={program,slot}` | WS (re)connect attempts. |\n| `grpc_reconnects_total` | counter | `source={program,slot}` | Yellowstone gRPC (re)connect attempts (only when `--grpc-url` is set). |\n| `cache_events_total` | counter | `kind={insert,update,remove}` | Cache mutations driven by `programSubscribe`. |\n| `sweep_duration_seconds` | histogram | — | Wall time per slot-tick sweep (scan + fire). Buckets target sub-10 ms. |\n| `rpc_errors_total` | counter | `op={get_program_accounts,get_latest_blockhash,send_transaction}` | RPC call errors, by failing operation. |\n\nUseful alerts:\n\n- `increase(hydra_cranker_current_slot[1m]) \u003c 100` — WS wedged.\n- `hydra_cranker_cranks_cached == 0` and `hydra_cranker_ws_reconnects_total \u003e 2` — not subscribed / flaky endpoint.\n- `rate(hydra_cranker_triggers_submitted_total{result=\"err\"}[5m]) / rate(hydra_cranker_triggers_submitted_total[5m]) \u003e 0.5` — majority of triggers failing.\n- `hydra_cranker_eligible_now \u003e 0` for \u003e30 s with no `rate(triggers_submitted_total[1m])` — have work, not doing it.\n- `histogram_quantile(0.99, rate(hydra_cranker_sweep_duration_seconds_bucket[5m])) \u003e 0.05` — sweep p99 \u003e 50 ms, perf regression or cache bloat.\n- `rate(hydra_cranker_rpc_errors_total[5m]) \u003e 0.1` — RPC endpoint failing a notable fraction of calls.\n\n## Instruction Reference\n\n| Disc | Name | Accounts | Data |\n|---:|---|---|---|\n| 0 | `Create` | `payer(w,s), crank(w), system_program` | schedule payload |\n| 1 | `Trigger` | `crank(w), cranker(w,s), instructions_sysvar` | none |\n| 2 | `Cancel` | `authority(s), crank(w), recipient(w)` | none |\n| 3 | `Close` | `reporter(s,w), crank(w), recipient(w)` | none |\n\nTo add lamports to a live crank, send a plain `system_program::transfer` to\nthe crank PDA — no dedicated instruction exists.\n\n## Limits\n\n- `Trigger` is top-level only\n- scheduled instructions cannot include signer metas\n- `MAX_ACCOUNTS = 32`\n- `MAX_DATA_LEN = 1024`\n- reward is fixed at `10_000` lamports plus the stored priority tip\n\n## Releasing\n\n`hydra-api` is the only crate published to crates.io (`hydra` is a program,\nnot a library; `hydra-cranker` / the examples are workspace-local).\n\nRelease flow:\n\n1. Bump `[workspace.package] version` in the root `Cargo.toml` (e.g. `0.1.1`).\n2. Commit + tag with a matching `vX.Y.Z` tag and push both.\n3. Create a GitHub release from that tag.\n\n`.github/workflows/release.yml` triggers on `release: published`, verifies\nthe tag matches `hydra-api`'s manifest version, dry-runs the package, then\n`cargo publish -p hydra-api`. Requires a `CARGO_REGISTRY_TOKEN` repo\nsecret (a crates.io API token scoped to publish-new + publish-update).\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmagicblock-labs%2Fhydra","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmagicblock-labs%2Fhydra","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmagicblock-labs%2Fhydra/lists"}