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
58 changes: 44 additions & 14 deletions .github/workflows/publish-rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,16 @@ jobs:
;;
esac

test:
name: Run release tests
needs: guard-allowed-branch
uses: ./.github/workflows/test.yml
secrets: inherit

publish:
name: Publish subscriptions-client crate
name: Publish subscriptions crate
runs-on: ubuntu-latest
needs: guard-allowed-branch
needs: [guard-allowed-branch, test]
defaults:
run:
working-directory: clients/rust
Expand All @@ -61,15 +67,36 @@ jobs:
with:
components: rustfmt, clippy

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'

- name: Setup pnpm
uses: pnpm/action-setup@v4

- name: Install dependencies
working-directory: .
run: pnpm install --frozen-lockfile

- name: Generate IDL and clients
working-directory: .
run: |
cd program && cargo build
cd .. && pnpm run generate-clients

- name: Build check
run: cargo build

- name: Verify crate package
run: cargo publish --dry-run --locked --allow-dirty

- name: Get current version
id: version
run: |
VERSION=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version')
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Publishing subscriptions-client v$VERSION"
echo "Publishing subscriptions v$VERSION"

- name: Check if prerelease
id: prerelease
Expand All @@ -80,6 +107,17 @@ jobs:
echo "is_prerelease=false" >> $GITHUB_OUTPUT
fi

- name: Authenticate crates.io trusted publisher
if: ${{ inputs.publish-to-crates }}
id: crates_io_auth
uses: rust-lang/crates-io-auth-action@bbd81622f20ce9e2dd9622e3218b975523e45bbe # v1.0.4

- name: Publish to crates.io
if: ${{ inputs.publish-to-crates }}
env:
CARGO_REGISTRY_TOKEN: ${{ steps.crates_io_auth.outputs.token }}
run: cargo publish --locked --allow-dirty

- name: Create and push tag
working-directory: .
run: |
Expand All @@ -92,14 +130,6 @@ jobs:
fi
git push origin "$TAG" 2>/dev/null || echo "Tag already pushed"

- name: Authenticate crates.io trusted publisher
if: ${{ inputs.publish-to-crates }}
uses: rust-lang/crates-io-auth-action@bbd81622f20ce9e2dd9622e3218b975523e45bbe # v1.0.4

- name: Publish to crates.io
if: ${{ inputs.publish-to-crates }}
run: cargo publish --locked

- name: Create GitHub Release
if: ${{ inputs.create-github-release }}
uses: actions/github-script@v8
Expand Down Expand Up @@ -127,20 +157,20 @@ jobs:
repo: context.repo.repo,
tag_name: tagName,
name: releaseName,
body: `Release of subscriptions-client v${{ steps.version.outputs.version }}\n\n**crates.io:** https://crates.io/crates/subscriptions-client/${{ steps.version.outputs.version }}`,
body: `Release of subscriptions v${{ steps.version.outputs.version }}\n\n**crates.io:** https://crates.io/crates/subscriptions/${{ steps.version.outputs.version }}`,
draft: false,
prerelease: isPrerelease,
});

- name: Publish summary
run: |
echo "## subscriptions-client Published" >> $GITHUB_STEP_SUMMARY
echo "## subscriptions Published" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Version**: \`${{ steps.version.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY
echo "**Tag**: \`rust-client-v${{ steps.version.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${{ inputs.publish-to-crates }}" == "true" ]; then
echo "Published to crates.io: https://crates.io/crates/subscriptions-client/${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo "Published to crates.io: https://crates.io/crates/subscriptions/${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
else
echo "Skipped crates.io publish" >> $GITHUB_STEP_SUMMARY
fi
40 changes: 9 additions & 31 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ jobs:
skip-build: 'false'

# ============================================
# mainnet: bundle into Squads multisig proposal
# mainnet: prepare Squads-owned buffers
# ============================================
- if: inputs.network == 'mainnet'
id: metadata-buffer
Expand All @@ -137,34 +137,11 @@ jobs:
priority-fees: ${{ inputs.priority-fee }}

- if: inputs.network == 'mainnet'
id: verify-pda
name: Build verify PDA transaction (mainnet)
uses: solana-developers/github-actions/verify-build@eb606791e11d06eb92593dfd3404bf0d4c809121
with:
program: ${{ env.PROGRAM }}
program-id: ${{ env.PROGRAM_ID }}
rpc-url: ${{ env.RPC_URL }}
keypair: ${{ env.DEPLOYER_KEYPAIR }}
repo-url: ${{ env.REPO_URL }}
network: mainnet
mount-path: program
commit-hash: ${{ github.sha }}
use-squads: 'true'
vault-address: ${{ env.SQUADS_VAULT }}
skip-build: 'true'

- if: inputs.network == 'mainnet'
name: Propose Squads upgrade transaction (mainnet)
uses: solana-developers/squads-program-action@1b4575c62c01ffc5fae9316f61486f7d37cb7b91 # v0.4.3
with:
rpc: ${{ env.RPC_URL }}
program: ${{ env.PROGRAM_ID }}
buffer: ${{ steps.write-buffer.outputs.buffer }}
metadata-buffer: ${{ steps.metadata-buffer.outputs.buffer }}
multisig: ${{ env.SQUADS_MULTISIG }}
keypair: /tmp/deployer.json
priority-fee: ${{ inputs.priority-fee }}
pda-tx: ${{ steps.verify-pda.outputs.pda_tx }}
name: Confirm mainnet buffer handoff
run: |
: "${SQUADS_VAULT:?Set SQUADS_VAULT in Doppler prd_github}"
echo "Program buffer authority assigned to Squads vault: $SQUADS_VAULT"
echo "IDL metadata buffer authority assigned to Squads vault: $SQUADS_VAULT"

- name: Cleanup keypair
if: always()
Expand All @@ -179,6 +156,7 @@ jobs:
echo "- Buffer: \`${{ steps.write-buffer.outputs.buffer }}\`" >> $GITHUB_STEP_SUMMARY
if [ "${{ inputs.network }}" = "mainnet" ]; then
echo "- IDL buffer: \`${{ steps.metadata-buffer.outputs.buffer }}\`" >> $GITHUB_STEP_SUMMARY
echo "- Squads multisig: \`${{ env.SQUADS_MULTISIG }}\`" >> $GITHUB_STEP_SUMMARY
echo "- **Action required**: review + approve transaction in Squads UI" >> $GITHUB_STEP_SUMMARY
echo "- Buffer authority: \`${{ env.SQUADS_VAULT }}\`" >> $GITHUB_STEP_SUMMARY
echo "- **Action required**: create the Squads upgrade proposal using the listed buffers" >> $GITHUB_STEP_SUMMARY
echo "- CI keypair role: fee payer and buffer writer only; it does not need Squads membership" >> $GITHUB_STEP_SUMMARY
fi
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ Audited by Cantina. See [audits/AUDIT_STATUS.md](audits/AUDIT_STATUS.md) for the

## Workspace Structure

- `program/` — Pinocchio program (workspace member `subscriptions`)
- `clients/rust/` — Codama-generated Rust client (`subscriptions-client`)
- `program/` — Pinocchio program (workspace member `subscriptions-program`)
- `clients/rust/` — Codama-generated Rust client (`subscriptions`)
- `clients/typescript/` — Hand-written SDK wrapping Codama-generated TS (`@solana/subscriptions`)
- `webapp/` — Vite + React 19 + Node API demo (faucet, deploy wizard, marketplace)
- `docs/` — Numbered ADRs
Expand Down
36 changes: 18 additions & 18 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ Delegation accounts include a version field and the program implements a three-t

This repository contains:

- A Rust Solana program built with [Pinocchio](https://github.com/febo/pinocchio)
- A Rust Solana program built with [Pinocchio](https://github.com/anza-xyz/pinocchio)
- IDL generation via [Codama](https://github.com/codama-idl/codama)
- Generated clients via Codama:
- TypeScript client (`@solana/subscriptions`) in `clients/typescript`
- Rust client (`subscriptions-client`) in `clients/rust`
- Rust client (`subscriptions`) in `clients/rust`
- A local demo webapp in `webapp/`
- CI pipeline with build, test, lint, and CU benchmarking

Expand Down
4 changes: 3 additions & 1 deletion clients/rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
[package]
name = "subscriptions-client"
name = "subscriptions"
version = "0.1.0"
edition = "2021"
description = "Rust client for the Subscriptions Solana program"
license = { workspace = true }
repository = { workspace = true }
include = ["Cargo.toml", "src/**/*.rs"]

[lints]
workspace = true
Expand Down
12 changes: 10 additions & 2 deletions docs/004-program-upgrade-mechanism.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Program Upgrade Mechanism

Program upgrades are governed by a [Squads](https://squads.xyz/) multisig and deployed via [txtx/Surfpool](https://docs.surfpool.run/).
Program upgrades are governed by a [Squads](https://squads.xyz/) multisig. Mainnet CI only writes upgrade buffers and transfers buffer authority to the Squads vault; it does not use a Squads member keypair.

## Signer Configuration

Expand All @@ -23,6 +23,14 @@ signer "authority" "svm::squads" {

## Deploying / Upgrading

### Mainnet CI

Run the `Release` GitHub Actions workflow with `network = mainnet`. The workflow loads the deployer keypair from Doppler, builds the verified program, writes the program buffer, writes the program-metadata IDL buffer, and transfers both buffer authorities to the Squads vault.

The CI keypair is only a fee payer and buffer writer. It does not need to be a Squads member. After CI finishes, create the Squads upgrade proposal manually using the program buffer and IDL metadata buffer from the workflow summary.

### Surfpool Runbooks

Run the deployment runbook with the appropriate environment:

```bash
Expand Down Expand Up @@ -50,7 +58,7 @@ solana-verify get-executable-hash target/deploy/subscriptions.so

### 3. Hash the on-chain buffer

The buffer address is shown in the Squads proposal.
The buffer address is shown in the GitHub Actions release summary and in the Squads proposal.

```bash
solana-verify get-buffer-hash -u <RPC_URL> <BUFFER_ADDRESS>
Expand Down
10 changes: 5 additions & 5 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ test *args: unit-test (integration-test args) test-client

# Run Rust unit tests
unit-test:
cargo test -p subscriptions
cargo test -p subscriptions-program

# Backwards-compatible alias for the old recipe name
test-program: unit-test
Expand Down Expand Up @@ -270,31 +270,31 @@ clean:
# Check formatting without fixing
fmt-check:
@echo "Checking Rust formatting..."
@cargo fmt -p subscriptions -p tests-subscriptions --check
@cargo fmt -p subscriptions-program -p tests-subscriptions --check
@echo "Checking TypeScript formatting..."
@pnpm run format:check
@echo "✓ Format check passed"

# Auto-format all code
fmt:
@echo "Formatting Rust..."
@cargo fmt -p subscriptions -p tests-subscriptions
@cargo fmt -p subscriptions-program -p tests-subscriptions
@echo "Formatting TypeScript..."
@pnpm run format
@echo "✓ Code formatted"

# Lint with auto-fix
lint:
@echo "Linting Rust..."
@cargo clippy --workspace --exclude subscriptions-client --all-targets --no-deps --fix -- -D warnings
@cargo clippy --workspace --exclude subscriptions --all-targets --no-deps --fix -- -D warnings
@echo "Linting TypeScript..."
@pnpm run lint:fix
@echo "✓ Code linted"

# Check linting without fixing
lint-check:
@echo "Checking Rust lint..."
@cargo clippy --workspace --exclude subscriptions-client --all-targets --no-deps -- -D warnings
@cargo clippy --workspace --exclude subscriptions --all-targets --no-deps -- -D warnings
@echo "Checking TypeScript lint..."
@pnpm run lint
@echo "✓ Lint check passed"
Expand Down
3 changes: 2 additions & 1 deletion program/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
[package]
name = "subscriptions"
name = "subscriptions-program"
version = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
repository = { workspace = true }

[lib]
name = "subscriptions"
crate-type = ["lib", "cdylib"]

[lints]
Expand Down
5 changes: 4 additions & 1 deletion program/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ fn generate_idl() -> Result<(), Box<dyn std::error::Error>> {
let idl_json = codama.get_json_idl()?;

// Parse and format the JSON with pretty printing.
let parsed: serde_json::Value = serde_json::from_str(&idl_json)?;
let mut parsed: serde_json::Value = serde_json::from_str(&idl_json)?;
if let Some(program) = parsed.get_mut("program").and_then(serde_json::Value::as_object_mut) {
program.insert("name".to_string(), serde_json::Value::String("subscriptions".to_string()));
}
let mut formatted_json = serde_json::to_string_pretty(&parsed)?;
formatted_json.push('\n');

Expand Down
2 changes: 1 addition & 1 deletion tests/integration-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ repository = { workspace = true }
workspace = true

[dependencies]
subscriptions = { path = "../../program", features = ["no-entrypoint"] }
subscriptions = { package = "subscriptions-program", path = "../../program", features = ["no-entrypoint"] }
pinocchio = { workspace = true }
pinocchio-associated-token-account = { workspace = true }
pinocchio-system = { workspace = true }
Expand Down
Loading