Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/.ghaignore
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,8 @@ tokens/escrow/steel
tokens/pda-mint-authority/steel
tokens/spl-token-minter/steel
tokens/token-swap/steel
tokens/transfer-tokens/steel
tokens/transfer-tokens/steel

# justfile-driven example: built and tested by .github/workflows/just.yml,
# not the legacy pinocchio CI (it has no build-and-test script)
games/world-cup/pinocchio
2 changes: 2 additions & 0 deletions .github/.justignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Justfile-driven, self-contained example workspaces to skip in just.yml.
# One directory path per line; comments and blank lines are ignored.
108 changes: 108 additions & 0 deletions .github/workflows/just.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
name: Just

# Self-contained examples that drive their own build and test through a `justfile`
# (standalone Cargo/pnpm workspace, pinned toolchain, Codama clients, LiteSVM tests).
# Discovery key: any directory containing a `justfile`. Each must expose `setup`,
# `build`, and `test` recipes. Exclusions live in `.github/.justignore`.

on:
schedule:
- cron: "0 0 * * *"
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened]
branches:
- main

env:
CARGO_NET_RETRY: "10"
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
# See https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
discover:
runs-on: ubuntu-latest
permissions:
pull-requests: read
outputs:
projects: ${{ steps.find.outputs.projects }}
any: ${{ steps.find.outputs.any }}
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Discover justfile examples
id: find
run: |
# Strip comments/blanks from the ignore list into a regex (matches nothing if absent).
ignore_pattern=$(grep -v '^#' .github/.justignore 2>/dev/null | grep -v '^$' | tr '\n' '|' | sed 's/|$//')
[ -z "$ignore_pattern" ] && ignore_pattern='^$'

all=$(find . -name justfile -not -path '*/node_modules/*' -printf '%h\n' \
| sed 's#^\./##' | sort -u | grep -vE "$ignore_pattern" || true)

# On push/schedule run everything; on PR keep only examples with changed files.
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
changed=$(git diff --name-only "${{ github.event.pull_request.base.sha }}" HEAD)
# A workflow change re-runs every example.
if echo "$changed" | grep -q '^.github/workflows/just.yml$'; then
projects="$all"
else
projects=""
for p in $all; do
if echo "$changed" | grep -q "^$p/"; then
projects+="$p"$'\n'
fi
done
projects=$(echo "$projects" | grep -v '^$' || true)
fi
else
projects="$all"
fi

if [[ -n "$projects" ]]; then
echo "Examples to build and test:"
echo "$projects"
echo "any=true" >> "$GITHUB_OUTPUT"
echo "projects=$(echo "$projects" | jq -R -s -c 'split("\n") | map(select(length > 0))')" >> "$GITHUB_OUTPUT"
else
echo "No examples to build and test."
echo "any=false" >> "$GITHUB_OUTPUT"
echo "projects=[]" >> "$GITHUB_OUTPUT"
fi

build-and-test:
needs: discover
if: needs.discover.outputs.any == 'true'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
project: ${{ fromJson(needs.discover.outputs.projects) }}
name: ${{ matrix.project }}
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v4
- name: Use Node.js
uses: actions/setup-node@v5
with:
node-version-file: ${{ matrix.project }}/.nvmrc
check-latest: true
- name: Install just
uses: extractions/setup-just@v3
- name: Setup Solana
uses: heyAyushh/setup-solana@v5.9
with:
solana-cli-version: stable
- name: Build and test
working-directory: ${{ matrix.project }}
run: |
solana -V
rustc -V
just setup
just check-generated
just build
just test
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -336,5 +336,11 @@ Use a data source for offchain data (called an Oracle) to perform activities onc
Use Shank and Solita to generate IDLs and TypeScript clients for native Solana programs, the same way Anchor does for Anchor programs.

[native](./tools/shank-and-solita/native)
## Games
### World Cup bracket prediction

A bracket-prediction game: entrants pay a fee to submit a 32-game bracket, an oracle posts results, scores are tallied on-chain, and the unique winner sweeps the pot. A full Pinocchio + Codama project with a TypeScript client and a webapp.

[pinocchio](./games/world-cup/pinocchio)

---
2 changes: 1 addition & 1 deletion biome.jsonc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://biomejs.dev/schemas/2.4.12/schema.json",
"files": {
"includes": ["**", "!**/Assets"]
"includes": ["**", "!**/Assets", "!games/world-cup/**"]
},
"formatter": {
// Matches more existing code,
Expand Down
20 changes: 20 additions & 0 deletions games/world-cup/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# World Cup bracket prediction

A bracket-prediction game on Solana. Entrants pay a fixed fee to submit a
consistency-checked 32-game bracket; an admin oracle records results; a
permissionless `refresh_score` folds each bracket into a provable on-chain
tally; the unique winner sweeps the pot. The ranking key is total — score, then
goal-closeness, then earliest submission — so exactly one winner always exists.

Lifecycle: `init_config` → `submit_bracket` → `lock` → `post_result` /
`post_goals` → `refresh_score` → `finalize` → `claim`.

This is a faithful, self-contained port of the upstream project: a Pinocchio
on-chain program with Codama-generated TypeScript and Rust clients, LiteSVM
integration tests, and a Vite + React webapp shell. It pins its own toolchain
and dependencies (`pinocchio 0.11.1`, `codama`, `pinocchio-token-2022`) in a
nested workspace, so it builds and tests via its own `justfile` rather than the
repo's shared CI. See the [pinocchio](./pinocchio) directory for the source,
build commands, and tests.

[pinocchio](./pinocchio)
3 changes: 3 additions & 0 deletions games/world-cup/pinocchio/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# ADMNbyyTcGfd4CKkfryMuSX1hWy6tKGuyyuEwSSDwxh4
ADMIN=
RPC_URL="https://api.devnet.solana.com"
147 changes: 147 additions & 0 deletions games/world-cup/pinocchio/.github/actions/setup/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
name: 'Setup CI Environment'
description: 'Shared setup for Rust, Solana, and pnpm'

inputs:
install-rust:
description: 'Install Rust toolchain'
required: false
default: 'true'
rust-toolchain:
description: 'Rust toolchain to install'
required: false
default: '1.92'
rust-components:
description: 'Rust components to install'
required: false
default: 'rustfmt, clippy'
enable-rust-cache:
description: 'Enable Rust caching (restore/save)'
required: false
default: 'true'
save-rust-cache:
description: 'Save the Rust cache after this job (only enable for the build job)'
required: false
default: 'false'
rust-cache-key:
description: 'Key for the Rust cache'
required: false
default: 'build'
install-solana:
description: 'Install Solana CLI'
required: false
default: 'true'
solana-version:
description: "Solana CLI version to install (e.g., 'v4.0.0')"
required: false
default: 'v4.0.0'
install-just:
description: 'Install just'
required: false
default: 'true'
install-pnpm:
description: 'Install pnpm and Node.js'
required: false
default: 'true'
install-pnpm-dependencies:
description: 'Install pnpm dependencies'
required: false
default: 'true'

runs:
using: 'composite'
steps:
- name: Setup Rust
if: inputs.install-rust == 'true'
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ inputs.rust-toolchain }}
components: ${{ inputs.rust-components }}

- name: Rust cache
if: inputs.install-rust == 'true' && inputs.enable-rust-cache == 'true'
uses: Swatinem/rust-cache@v2
with:
workspaces: '. -> target'
shared-key: ${{ inputs.rust-cache-key }}
save-if: ${{ inputs.save-rust-cache }}
cache-on-failure: true

- name: Resolve Solana release
if: inputs.install-solana == 'true'
id: solana-release
shell: bash
run: |
version='${{ inputs.solana-version }}'
if [[ "$version" =~ ^[0-9] ]]; then
version="v$version"
fi
echo "version=$version" >> "$GITHUB_OUTPUT"

- name: Cache Solana CLI
if: inputs.install-solana == 'true'
id: solana-cache
uses: actions/cache@v4
with:
path: |
~/.local/share/solana
~/.cache/solana
key: ${{ runner.os }}-solana-${{ inputs.solana-version }}

- name: Install Solana CLI
if: inputs.install-solana == 'true' && steps.solana-cache.outputs.cache-hit != 'true'
shell: bash
run: |
curl -sSfL "https://release.anza.xyz/${{ steps.solana-release.outputs.version }}/install" | sh

- name: Add Solana to PATH
if: inputs.install-solana == 'true'
shell: bash
run: echo "$HOME/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH

- name: Create Solana keypair
if: inputs.install-solana == 'true'
shell: bash
run: |
mkdir -p ~/.config/solana
[ -f ~/.config/solana/id.json ] || solana-keygen new --no-bip39-passphrase -o ~/.config/solana/id.json

- name: Add local bin to PATH
shell: bash
run: echo "$HOME/.local/bin" >> $GITHUB_PATH

- name: Install just
if: inputs.install-just == 'true'
uses: extractions/setup-just@v4
with:
just-version: '1.50.0'

- name: Install pnpm
if: inputs.install-pnpm == 'true'
uses: pnpm/action-setup@v4

- name: Setup Node.js
if: inputs.install-pnpm == 'true'
uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'

- name: Get pnpm store directory
if: inputs.install-pnpm == 'true'
id: pnpm-store
shell: bash
run: echo "path=$(pnpm store path)" >> $GITHUB_OUTPUT

- name: Cache pnpm store
if: inputs.install-pnpm == 'true'
id: pnpm-cache
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-store.outputs.path }}
key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-

- name: Install pnpm dependencies
if: inputs.install-pnpm == 'true' && inputs.install-pnpm-dependencies == 'true'
shell: bash
run: pnpm install --frozen-lockfile
57 changes: 57 additions & 0 deletions games/world-cup/pinocchio/.github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Build

on:
pull_request:
push:
branches: [main]

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

permissions:
contents: read

env:
SOLANA_VERSION: '4.0.0'

jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 1

- uses: ./.github/actions/setup
with:
rust-cache-key: 'build'
save-rust-cache: 'true'
solana-version: ${{ env.SOLANA_VERSION }}

- name: Build program and client
run: just build

webapp-build:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 1

- uses: ./.github/actions/setup
with:
install-rust: 'false'
install-solana: 'false'
install-just: 'false'
enable-rust-cache: 'false'

- name: Generate and build TypeScript client
run: |
pnpm run generate-clients
pnpm --filter @solana/world-cup build

- name: Build webapp
run: pnpm --filter webapp build
Loading
Loading