Skip to content

Repository files navigation

Hedwig

Hedwig is shared authorization for related Anchor programs: one canonical role store, with each consumer still responsible for authenticating its actor.

Assign a named role to any pubkey, set optional membership expiry, disable the role in an incident, and let related Solana programs verify active membership through direct or CPI-safe checks.

Hedwig is a devnet-stage Anchor program. The repository implements six instructions, a secure reference consumer, and a repository-local TypeScript SDK alpha. The local suites contain 40 LiteSVM integration tests and 27 SDK tests. The reviewed core and consumer are both live on devnet. On 2026-07-24, the SDK completed the six-instruction lifecycle and an authenticated member changed state in the separate consumer through CPI.

Devnet program: H4J9wWhraK2Zvn4o9aFheFVmAf7nfaBNPw3d7w77X1eC

Devnet reference consumer: 52D3pTYvMwLYbiigY5xg55n4HmtEzTKCEicx1Cojzo9a

Why Hedwig

Teams that maintain related Solana programs often repeat account layouts, authorization rules, expiry behavior, and revocation flows. Those copies can drift: a role recognized by one consumer has no shared meaning in another.

Hedwig keeps membership in one small onchain store:

  • an Org is a role namespace;
  • a Role is a named authority within that org;
  • a Member records that one pubkey holds one role; and
  • check_role is the CPI entrypoint other programs can use to verify active membership.

The holder can be a wallet, multisig, program-derived identity, or agent key. Hedwig records membership; each consumer authenticates its actor and decides what that role permits.

Current status

Surface Current state
Anchor program Six instructions implemented
Rust tests 40 LiteSVM integration tests across the core and consumer
TypeScript tests 27 SDK tests plus SDK and app typechecks
Devnet evidence Upgrade verified at slot 478655638; six-instruction lifecycle finalized afterward
Circuit breaker Live enabled=false state verified on devnet
TypeScript SDK Repository-local 0.1.0-alpha.0; built and tested, not published
Secure CPI consumer Deployed at slot 478667066; live Hedwig-gated state change verified
Upgrade authority Single deployer key; 2-of-3 Squads transfer planned before mainnet
Pilot validation Offer published; no signed pilot or external adoption
Network Devnet; mainnet is planned

See ROADMAP.md for evidence-gated delivery milestones and THREAT-MODEL.md for the current trust boundaries.

Deployment evidence was checked on 2026-07-24. The live program was upgraded at slot 478655638; its upgrade-authority pubkey remains 8gbaJEfM5VDs9BpFLgwMTq7s2FkVpEri8ZnPbxn4HPqY. The reviewed ELF SHA-256 is 42670041e7df0f9832930bfa511b884e8b59ceebc8e14b0638c4627a83e6aed3. See the promotion record for the transaction, loader-padding proof, and six lifecycle signatures.

The project received USDG 1,500 from a USDG 3,000 grant. The maintainer reports that the authenticated portal exposes a final-tranche request form and the grant program's general completion policy. This portal state is not publicly verifiable, and no project-specific milestone or KPI text was found. Progress is tracked through the evidence gates in ROADMAP.md and the public grant progress ledger, without treating the reference consumer as independent adoption.

The next product evidence gate is one signed seven-day pilot for a real state-changing path across two related Anchor programs. The public pilot program defines the offer and its limits. No signed pilot, design partner, customer, or external adoption has been verified.

Instructions

Instruction Authorization Effect
create_org Authority signs Creates the authority's org namespace
create_role Org authority signs Creates an enabled named role under the org
assign_role Role admin signs Creates a member PDA, optionally with expiry
revoke_role Role admin signs Closes the member PDA and returns its rent to the admin
check_role No signer required Returns success only for an enabled, unexpired membership
set_role_enabled Role admin signs Enables or disables checks and new assignments for the role

The current program creates one org per authority. A role's admin is initialized to that org authority and cannot yet be changed. Disabling a role preserves its member accounts while causing check_role and new assign_role calls to fail.

Account model

Org PDA       ["org", authority]
  └─ Role PDA ["role", org, role_name]
       └─ Member PDA ["member", role, holder]

The PDA seeds bind the namespace, role, and holder. Anchor account ownership, deserialization, seed, bump, and has_one constraints establish the account relationships used by each instruction.

Role names are part of the role PDA seed and are therefore immutable. Membership expiry uses a Unix timestamp; 0 means no expiry. Revocation closes the member account, so a later grant creates it again.

Integrating with check_role

check_role proves that the supplied holder pubkey has a valid member PDA for the supplied role, that the role is enabled, and that the membership has not expired. It does not prove that the transaction actor controls that holder.

A consuming program must authenticate the actor first, for example with a Signer<'info> for a wallet or with its own validated PDA constraints, and pass that authenticated account as holder:

hedwig_sol::cpi::check_role(CpiContext::new(
    ctx.accounts.hedwig_program.key(),
    hedwig_sol::cpi::accounts::CheckRole {
        member: ctx.accounts.member.to_account_info(),
        role: ctx.accounts.role.to_account_info(),
        holder: ctx.accounts.actor.to_account_info(),
    },
))?;

The CPI returns Ok(()) on active membership and a Hedwig error otherwise. With the ? shown above, a failed check aborts the consuming instruction. Hedwig does not return a boolean and does not grant transaction authority by itself.

The Rust CPI interface comes from the program crate. The compiling reference consumer, its negative tests, and the TypeScript client flow are documented in the integration guide.

The reference consumer is live on devnet. Its first verified state change used an authenticated signer as the Hedwig holder and incremented a separate consumer-owned counter from zero to one. This is deployment and integration evidence, not a design-partner or customer claim.

Run locally

Requirements: Rust, Anchor CLI 1.0.2, and Solana/Agave CLI 4.0.1 or newer.

cargo fmt --check
cargo build
cargo build-sbf --manifest-path programs/hedwig_consumer/Cargo.toml
cargo build-sbf --manifest-path programs/hedwig_sol/Cargo.toml
cargo test --workspace
yarn sdk:typecheck
yarn sdk:test
./node_modules/.bin/tsc -p app/tsconfig.json --noEmit

The tests run without a network connection. Build both SBF artifacts before the workspace tests because the LiteSVM fixtures load them at compile time. To run the membership lifecycle against devnet, see app/README.md.

Repository guide

License

MIT. See LICENSE.

About

Composable onchain roles for Solana. Grant roles, not keys.

Topics

Resources

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages