Skip to content

feat(tokens/hackathon): Anchor 1.0 hackathon prize program with Squads multisig committee#21

Closed
mikemaccana wants to merge 2 commits into
mainfrom
feat/hackathon-multisig
Closed

feat(tokens/hackathon): Anchor 1.0 hackathon prize program with Squads multisig committee#21
mikemaccana wants to merge 2 commits into
mainfrom
feat/hackathon-multisig

Conversation

@mikemaccana
Copy link
Copy Markdown
Collaborator

Summary

Adds tokens/hackathon/anchor/ — an Anchor 1.0 program for running a
hackathon prize pool where a Squads multisig committee controls prize
creation and award decisions, but anyone can trigger the actual on-chain
payment once a winner is recorded.

The program is multisig-agnostic: it stores a single authority: Pubkey
per hackathon and checks signer == authority on privileged handlers. In
practice that pubkey is a Squads v4 vault PDA, but you could swap in any
other multisig without touching the program.

Instruction handlers

Handler Signer Behaviour
create_hackathon Multisig Initialise Hackathon under authority.
add_prize Multisig Register a Prize with its own mint and a new vault ATA.
set_winner Multisig Record the winner pubkey for a prize.
pay_winner Anyone Transfer exactly prize.amount to the winner's token account.
cancel_prize Multisig Drain the vault to a refund target and lock the prize against payout.
close_hackathon Multisig Refund Hackathon rent once every prize is paid or cancelled.

pay_winner being permissionless is deliberate. Once the committee has
voted, anyone — the winner, a bot, an organiser's intern — can submit the
payout transaction.

Tests

LiteSVM-based Rust integration tests build a real Squads v4 2-of-3
multisig (Alice / Bob / Carol) and drive the program end-to-end through
Squads' propose / vote / execute flow. 9 tests, all green:

  • Happy path (1): create → add_prize → fund → set_winner (via
    multisig vote) → pay_winner (signed by an unrelated bystander wallet).
    Asserts winner balance equals prize.amount.
  • Failure cases (4): pay_winner rejects when no winner is set,
    vault is under-funded, or prize is already paid. set_winner rejects a
    non-multisig signer.
  • Lifecycle (3): cancel_prize drains a funded vault and locks the
    prize. close_hackathon succeeds once every prize is resolved, fails
    while any prize is still active.

Squads integration notes

  • Squads v4 on-chain program is vendored as a 1.5 MB .so fixture at
    programs/hackathon/tests/fixtures/squads_multisig.so, dumped from
    mainnet (SQDS4ep65T869zMMBKyuUq6aD6EgTu8psMjkvj52pCf). The README has
    the refresh command. The repo's top-level .gitignore already
    whitelists **/tests/fixtures/*.so.
  • We do not depend on the squads-multisig SDK crate — it pulls in
    solana-client 1.17, which conflicts with the Anchor 1.0 / Solana 3.x
    stack on zeroize. Instead, instruction builders are hand-rolled in
    tests/common/squads.rs (Anchor 8-byte discriminators + Borsh args +
    the SmallVec<u8, T> wire format used by the compiled-message
    TransactionMessage struct).
  • The Squads ProgramConfig account (normally written by a Squads admin
    instruction we cannot run from a test fixture) is forged directly into
    LiteSVM with multisig_creation_fee = 0.

Files

tokens/hackathon/anchor/
├── Anchor.toml
├── Cargo.toml
├── README.md
├── .gitignore
└── programs/hackathon/
    ├── Cargo.toml
    ├── Xargo.toml
    ├── src/
    │   ├── lib.rs
    │   ├── error.rs
    │   ├── state/{mod,hackathon,prize}.rs
    │   └── instructions/{mod,shared,create_hackathon,add_prize,
    │                     set_winner,pay_winner,cancel_prize,
    │                     close_hackathon}.rs
    └── tests/
        ├── common/{mod,squads,world}.rs
        ├── fixtures/squads_multisig.so
        ├── test_hackathon_happy_path.rs
        ├── test_hackathon_failure_cases.rs
        └── test_hackathon_lifecycle.rs

Build & run

cd tokens/hackathon/anchor
cargo build-sbf
cargo test

cargo build-sbf must run first because the integration tests load the
compiled program .so via include_bytes!.

Stack

  • anchor-lang = "1.0.0", anchor-spl = "1.0.0"
  • litesvm = "0.11.0", solana-kite = "0.3.0"
  • SPL Token Interface throughout — works for both classic SPL Token and
    Token-2022 mints; the choice is per-prize at add_prize time.

Compiled program is 336 KB.

Edward (Edwardbot) added 2 commits May 13, 2026 21:43
A new Anchor 1.0 example: a hackathon prize program whose authority is an
external Squads multisig vault PDA. The on-chain program is multisig-agnostic
- it stores an opaque `authority` pubkey and checks `signer == authority` on
each privileged handler. Squads handles propose/vote/execute off-program.

Instruction handlers:
- create_hackathon: open a hackathon under `authority`
- add_prize: register a prize with its own mint and vault ATA
- set_winner: record the winning pubkey for a prize (multisig)
- pay_winner: transfer exactly `prize.amount` to the recorded winner.
  Unpermissioned: anyone can call once the winner is set and the vault is
  funded.
- cancel_prize: drain the vault back to a refund target and lock the prize
  (multisig)
- close_hackathon: refund Hackathon rent once every prize is paid or
  cancelled (multisig)

Uses the SPL Token Interface throughout so the same compiled program works
for both classic SPL Token and Token-2022 mints. Per-prize mint and per-prize
vault PDA so one hackathon can mix denominations and surface a clean
PDA-derivation pattern.

This commit is program code + smoke build only; LiteSVM tests with a real
Squads 2-of-3 (Alice/Bob/Carol) committee follow in the next commit.
Integration tests build a real Squads v4 multisig (Alice/Bob/Carol,
threshold 2-of-3) inside LiteSVM and drive the hackathon program end-to-end
through the propose / vote / execute flow.

Coverage (9 tests total):
- happy path: create_hackathon -> add_prize -> fund -> set_winner (via
  multisig vote) -> pay_winner (signed by an unrelated bystander wallet).
  Asserts the winner's token balance equals prize.amount.
- pay_winner failure cases: no winner set, vault under-funded, already
  paid.
- set_winner failure case: non-multisig signer rejected by Anchor's
  has_one = authority constraint.
- cancel_prize: drains a funded vault to a refund target, closes the
  vault, locks the prize against future pay_winner.
- close_hackathon: succeeds once every prize is paid or cancelled; fails
  while any prize is still active.

Implementation notes:

- Squads on-chain program is vendored as a 1.5 MB .so fixture dumped from
  mainnet (SQDS4ep65T869zMMBKyuUq6aD6EgTu8psMjkvj52pCf). The README has
  the refresh command.
- The Squads SDK crates (squads-multisig, squads-multisig-program) pull
  in solana-client 1.17, which conflicts with the Anchor 1.0 / Solana 3.x
  stack on zeroize. Instead, instruction builders are hand-rolled in
  tests/common/squads.rs: Anchor 8-byte discriminators (sha256("global:
  <name>")[..8]), Borsh args, plus the SmallVec<u8, T> wire format used
  by the compiled-message TransactionMessage struct.
- The Squads ProgramConfig account (which would normally be initialised
  by a Squads admin instruction we cannot run) is forged directly into
  LiteSVM via set_account with multisig_creation_fee = 0.

Also adds the README and bumps Cargo.toml with the sha2 dev-dependency
used by the Squads helper module.
@mikemaccana-edwardbot
Copy link
Copy Markdown

Superseded by #22. Same program, but with on-chain/off-chainonchain/offchain normalisation applied across 5 files per the repo-wide convention. Closing this in favour of #22.

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