Skip to content
Open
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
49 changes: 30 additions & 19 deletions .github/actions/setup/action.yml
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Leftover Photon steps reference removed inputs, causing CI failures for all workflows

The old Photon indexer steps (lines 60-73) were not removed during the refactor and reference inputs.photon-version and inputs.photon-commit, which are no longer declared as inputs. These steps have no if condition, so they execute unconditionally in every workflow that uses this action.

Root Cause and Impact

Line 65 builds a cache key using undefined inputs:

key: photon-${{ runner.os }}-${{ inputs.photon-version }}-${{ inputs.photon-commit }}

Both inputs.photon-version and inputs.photon-commit resolve to empty strings, producing a degenerate cache key like photon-Linux--.

Line 68 conditionally installs Photon when the cache misses, but line 72 runs:

cargo install --git https://github.com/helius-labs/photon.git --rev  --locked --force

The empty --rev flag will cause cargo install to fail.

Additionally, there's a duplicate step ID cache-photon at lines 61 and 81. GitHub Actions does not allow duplicate step IDs in composite actions — the second definition shadows the first, making the conditional steps.cache-photon.outputs.cache-hit at line 88 reference the wrong step.

Impact: The Rust test workflow (rust-tests.yml) does not pass photon-indexer: "true", so the old unconditional steps at lines 60-73 will run and fail. The TypeScript workflow passes photon-indexer: "true" but the duplicate ID issue means the new Photon cache step (line 81) conflicts with the old one (line 61).

(Refers to lines 60-73)

Prompt for agents
In .github/actions/setup/action.yml, remove the leftover old Photon indexer steps at lines 60-73 (the 'Cache Photon indexer' step with id cache-photon that references inputs.photon-version and inputs.photon-commit, and the 'Install Photon indexer' step that references the same undefined inputs). These are remnants from the old version and conflict with the new conditional Photon steps at lines 79-92. The old steps have no 'if' condition so they run unconditionally and will fail because the inputs they reference (photon-version, photon-commit) no longer exist.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Setup Environment
description: Setup Rust, Solana CLI, and Light CLI for testing
description: Setup Rust, Solana CLI, and optionally Node.js for testing

inputs:
example:
Expand All @@ -8,23 +8,19 @@ inputs:
solana-cli-version:
description: "Solana CLI version"
required: false
default: "2.1.21"
default: "2.3.11"
rust-toolchain:
description: "Rust toolchain version"
required: false
default: "1.85.0"
default: "1.90.0"
light-cli-version:
description: "Light CLI version"
required: false
default: "0.27.1-alpha.11"
photon-version:
description: "Photon indexer version"
default: "alpha"
photon-indexer:
description: "Install Photon indexer (required for TypeScript tests)"
required: false
default: "0.51.2"
photon-commit:
description: "Photon indexer commit hash"
required: false
default: "ac7df6c388db847b7693a7a1cb766a7c9d7809b5"
default: "false"

runs:
using: composite
Expand All @@ -33,18 +29,19 @@ runs:
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: ${{ inputs.rust-toolchain }}
cache-workspaces: ${{ inputs.example }}
cache-workspaces: ${{ inputs.example || '.' }}

- name: Setup Node.js (for Light CLI)
- name: Setup Node.js
if: inputs.node-version != ''
uses: actions/setup-node@v4
with:
node-version: "22"
node-version: ${{ inputs.node-version }}
Comment on lines +34 to +38
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Missing node-version input declaration causes Node.js setup to always fall through to default

The composite action references inputs.node-version in conditions at lines 35 and 41, but node-version is never declared in the inputs: section (lines 4-23).

Root Cause and Impact

The TypeScript workflow at .github/workflows/typescript-tests.yml:31 passes node-version: ${{ env.NODE_VERSION }} to the action. However, since node-version is not declared as an input in the action's inputs: block, inputs.node-version will always evaluate to an empty string.

This means:

  • Line 35: if: inputs.node-version != '' → always false, so the custom Node.js version step is never executed
  • Line 41: if: inputs.node-version == '' → always true, so the fallback Node.js 22 step always runs

In this specific case, the TypeScript workflow passes NODE_VERSION: "22" which happens to match the fallback, so the behavior is accidentally correct. But the intent to allow callers to specify a custom Node.js version is broken — any non-22 version passed as node-version would be silently ignored.

Prompt for agents
In .github/actions/setup/action.yml, add a `node-version` input declaration to the `inputs:` section (around line 20-23). It should be optional with no default (or default empty string), for example:

  node-version:
    description: "Node.js version (defaults to 22 if not specified)"
    required: false
    default: ""

This will allow the conditions at lines 35 and 41 that reference `inputs.node-version` to work correctly when callers pass a custom Node.js version.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


- name: Cache npm global packages
uses: actions/cache@v4
- name: Setup Node.js (for Light CLI)
if: inputs.node-version == ''
uses: actions/setup-node@v4
with:
path: ~/.npm
key: npm-${{ runner.os }}-light-cli-${{ inputs.light-cli-version }}
node-version: "22"

- name: Cache Solana CLI tools
uses: actions/cache@v4
Expand Down Expand Up @@ -79,6 +76,21 @@ runs:
shell: bash
run: npm install -g @lightprotocol/zk-compression-cli@${{ inputs.light-cli-version }}

- name: Cache Photon indexer
if: inputs.photon-indexer == 'true'
id: cache-photon
uses: actions/cache@v4
with:
path: ~/.cargo/bin/photon
key: photon-${{ runner.os }}-ac7df6c388db847b7693a7a1cb766a7c9d7809b5

- name: Install Photon indexer
if: inputs.photon-indexer == 'true' && steps.cache-photon.outputs.cache-hit != 'true'
shell: bash
env:
RUSTFLAGS: "-A dead-code"
run: cargo install --git https://github.com/lightprotocol/photon.git --rev ac7df6c388db847b7693a7a1cb766a7c9d7809b5 --locked --force

- name: Generate keypair
shell: bash
run: solana-keygen new --no-bip39-passphrase
Expand All @@ -90,4 +102,3 @@ runs:
cargo --version
solana --version
light --version

27 changes: 10 additions & 17 deletions .github/workflows/rust-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,33 +19,26 @@ env:

jobs:
test-rust:
name: ${{ matrix.name }}
name: ${{ matrix.package }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- example: anchor
package: escrow
name: anchor/escrow
- example: anchor
package: light-token-minter
name: anchor/light-token-minter
package:
- escrow
- fundraiser
- light-token-minter
- swap_example
steps:
- uses: actions/checkout@v4

- name: Setup environment
uses: ./.github/actions/setup
with:
example: ${{ matrix.example }}
example: programs/anchor
solana-cli-version: ${{ env.SOLANA_CLI_VERSION }}
rust-toolchain: ${{ env.RUST_TOOLCHAIN }}

- name: Build and test
working-directory: ${{ matrix.example }}
run: |
if [ -n "${{ matrix.package }}" ]; then
cargo test-sbf -p ${{ matrix.package }}
else
cargo test-sbf
fi
- name: Test
working-directory: programs/anchor
run: cargo test-sbf -p ${{ matrix.package }}
48 changes: 7 additions & 41 deletions .github/workflows/typescript-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ concurrency:
cancel-in-progress: true

env:
SOLANA_CLI_VERSION: "2.1.21"
SOLANA_CLI_VERSION: "2.3.11"
RUST_TOOLCHAIN: "1.90.0"
NODE_VERSION: "22"

jobs:
Expand All @@ -24,48 +25,13 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
- name: Setup environment
uses: ./.github/actions/setup
with:
node-version: ${{ env.NODE_VERSION }}

- name: Cache Solana CLI tools
uses: actions/cache@v4
with:
path: |
~/.cache/solana/
~/.local/share/solana/
key: solana-cli-${{ runner.os }}-${{ env.SOLANA_CLI_VERSION }}

- name: Install Solana CLI tools
run: |
sh -c "$(curl -sSfL https://release.anza.xyz/v${{ env.SOLANA_CLI_VERSION }}/install)"
echo "/home/runner/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH

- name: Install Light CLI
run: npm install -g @lightprotocol/zk-compression-cli@alpha

- name: Install Rust (for Photon)
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
cache: false

- name: Cache Photon indexer
id: cache-photon
uses: actions/cache@v4
with:
path: ~/.cargo/bin/photon
key: photon-${{ runner.os }}-1a785036de52896b68d06413e3b0231122d6aa4a

- name: Install Photon indexer
if: steps.cache-photon.outputs.cache-hit != 'true'
run: cargo install --git https://github.com/lightprotocol/photon.git --rev 1a785036de52896b68d06413e3b0231122d6aa4a --locked
env:
RUSTFLAGS: "-A dead-code"

- name: Generate keypair
run: solana-keygen new --no-bip39-passphrase
solana-cli-version: ${{ env.SOLANA_CLI_VERSION }}
rust-toolchain: ${{ env.RUST_TOOLCHAIN }}
photon-indexer: "true"
Comment on lines +28 to +34
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Missing example input in TypeScript workflow causes required input validation failure

The TypeScript workflow at .github/workflows/typescript-tests.yml:28-34 invokes the setup action but does not pass the example input, which is declared as required: true in .github/actions/setup/action.yml:6-7.

Root Cause and Impact

The setup action declares:

inputs:
  example:
    description: "Example directory path"
    required: true

But the TypeScript workflow only passes:

with:
  node-version: ${{ env.NODE_VERSION }}
  solana-cli-version: ${{ env.SOLANA_CLI_VERSION }}
  rust-toolchain: ${{ env.RUST_TOOLCHAIN }}
  photon-indexer: "true"

The example input is missing. GitHub Actions will produce a warning for missing required inputs in composite actions. The cache-workspaces at line 32 uses ${{ inputs.example || '.' }} which falls back to ., so the Rust cache will target the repo root instead of a specific workspace directory. This may cause cache misses or incorrect caching for the TypeScript workflow.

Suggested change
- name: Setup environment
uses: ./.github/actions/setup
with:
node-version: ${{ env.NODE_VERSION }}
- name: Cache Solana CLI tools
uses: actions/cache@v4
with:
path: |
~/.cache/solana/
~/.local/share/solana/
key: solana-cli-${{ runner.os }}-${{ env.SOLANA_CLI_VERSION }}
- name: Install Solana CLI tools
run: |
sh -c "$(curl -sSfL https://release.anza.xyz/v${{ env.SOLANA_CLI_VERSION }}/install)"
echo "/home/runner/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH
- name: Install Light CLI
run: npm install -g @lightprotocol/zk-compression-cli@alpha
- name: Install Rust (for Photon)
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
cache: false
- name: Cache Photon indexer
id: cache-photon
uses: actions/cache@v4
with:
path: ~/.cargo/bin/photon
key: photon-${{ runner.os }}-1a785036de52896b68d06413e3b0231122d6aa4a
- name: Install Photon indexer
if: steps.cache-photon.outputs.cache-hit != 'true'
run: cargo install --git https://github.com/lightprotocol/photon.git --rev 1a785036de52896b68d06413e3b0231122d6aa4a --locked
env:
RUSTFLAGS: "-A dead-code"
- name: Generate keypair
run: solana-keygen new --no-bip39-passphrase
solana-cli-version: ${{ env.SOLANA_CLI_VERSION }}
rust-toolchain: ${{ env.RUST_TOOLCHAIN }}
photon-indexer: "true"
- name: Setup environment
uses: ./.github/actions/setup
with:
example: programs/anchor
node-version: ${{ env.NODE_VERSION }}
solana-cli-version: ${{ env.SOLANA_CLI_VERSION }}
rust-toolchain: ${{ env.RUST_TOOLCHAIN }}
photon-indexer: "true"
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


- name: Install dependencies
working-directory: typescript-client
Expand Down
22 changes: 19 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
# Dependencies
**/node_modules/
**/dist/

# Environment and secrets
**/.env
**/.env.*
**/*-keypair.json
**/id.json

# Build artifacts
**/target
**/.anchor

# Test artifacts
**/test-ledger

# Lock files
**/pnpm-lock.yaml
**/package-lock.json

# Backups
**/*.json.bak
**/test-ledger
**/target

# Project-specific
cli/accounts/
bugs.md
.claude
77 changes: 77 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What this repo is

Light Token example programs demonstrating rent-free token vaults using Light Protocol on Solana. Contains Anchor programs (escrow, fundraiser, token-swap, light-token-minter), a shared test utilities crate, TypeScript client examples, and toolkits (payments, streaming).

## Build and test

All Anchor programs live in `programs/anchor/` as a Cargo workspace. Build and test from that directory:

```bash
# Build a single program
cd programs/anchor && cargo build-sbf -p escrow

# Test a single program (runs LiteSVM-based tests, no validator needed)
cd programs/anchor && cargo test-sbf -p escrow
cd programs/anchor && cargo test-sbf -p fundraiser
cd programs/anchor && cargo test-sbf -p light-token-minter
cd programs/anchor && cargo test-sbf -p swap_example

# Run a single test by name
cd programs/anchor && cargo test-sbf -p escrow -- test_escrow_spl

# TypeScript client examples
cd typescript-client && npm install && npm run create-mint:action
```

CI runs `cargo test-sbf -p <package>` for each program (see `.github/workflows/rust-tests.yml`). Solana CLI 2.3.11, Rust 1.90.0.

## Architecture

### Programs (`programs/anchor/`)

All programs use `#[light_program]` + `#[program]` dual macros from Light Protocol. The `#[light_program]` macro generates `LightAccountVariant`, vault seed structs, and CPI signer boilerplate.

- **escrow** — Peer-to-peer token swap. `make_offer` creates an Offer account + rent-free Light vault; `take_offer` completes the swap and closes the vault. `Offer` derives `LightAccount` → generates `LightAccountVariant::Vault`, `VaultSeeds`, `OfferSeeds`.
- **fundraiser** — Crowdfunding. `initialize` creates a Fundraiser + vault; `contribute`, `check_contributions`, `refund`. `Fundraiser` uses `#[account]` only (no `LightAccount` derive) → no cold/hot vault loading possible.
- **token-swap** — AMM with `create_amm`, `create_pool`, `deposit_liquidity`, `withdraw_liquidity`, `swap_exact_tokens_for_tokens`, `create_pool_light_lp`.
- **light-token-minter** — Helper program to create Light mints with metadata and mint tokens.
- **shared-test-utils** — Reusable test helpers for SPL/T22/Light mint creation, ATA creation, SPL interface PDAs, balance verification. All programs depend on this for tests.

### Transfer routing (`shared.rs` in escrow/fundraiser)

Each program's `shared.rs` contains `transfer_tokens()` which picks the CPI path at runtime:
- Both accounts Light → `TransferCheckedCpi` (direct, no interface PDA)
- One SPL/T22 + one Light → `TransferInterfaceCpi` (cross-standard, requires SPL interface PDA)

### Token configurations tested

Each program tests 5 token standard combinations via `TokenConfig` enum:
- `Spl` — SPL mint + SPL ATAs + Light vault
- `Token2022` — T22 mint + T22 ATAs + Light vault
- `Light` — Light mint + Light ATAs + Light vault
- `LightSpl` — SPL mint, but user accounts converted to Light ATAs before escrow
- `LightT22` — T22 mint, but user accounts converted to Light ATAs before escrow

### Test setup pattern

Tests follow: `create_test_rpc()` → `setup_*_test(config)` → `create_token_account()`/`create_contributor()` → `run_*_full_flow()`. Common modules in `tests/common/mod.rs`.

### Cold/hot lifecycle (escrow only)

Light accounts auto-compress after rent expires. Escrow tests exercise the full cycle: create → `warp_to_compress` (calls `rpc.warp_epoch_forward(30)`) → `load_light_accounts` → transact. Fundraiser skips this because `Fundraiser` state lacks `LightAccount` derive.

### Local path dependencies

`light-client` and `light-program-test` use local path deps pointing to `~/Workspace/light-protocol/`. The workspace `Cargo.toml` also patches all `light-*` crates to local paths to avoid version conflicts.

## Key SDK patterns

- `CpiSigner` + `derive_light_cpi_signer!` at crate root for each program
- Constants used in `token::owner_seeds` must be `pub use`-exported from `lib.rs`
- Per-program rent sponsor PDA: `derive_rent_sponsor_pda(&program_id)` — separate from global `LIGHT_TOKEN_RENT_SPONSOR`
- `get_create_accounts_proof()` fetches validity proofs for account creation (needed for `init` instructions, not reads)
- `InitializeRentFreeConfig` sets up per-program compression config before any `#[light_account(init)]`
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,4 @@ TypeScript examples for light-token-sdk.

## Documentation

Learn more [about to Light-Token here](https://www.zkcompression.com/light-token/welcome).
Learn more [about Light-Token here](https://www.zkcompression.com/light-token/welcome).
30 changes: 30 additions & 0 deletions programs/anchor/Anchor.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[toolchain]

[features]
resolution = true
skip-lint = false

[workspace]
members = [
"escrow",
"fundraiser",
"light-token-minter",
"token-swap",
]

[programs.localnet]
escrow = "FKJs6rp6TXJtxzLiPtdYhqa9ExRuBXG2zwh4fda6WATN"
fundraiser = "Eoiuq1dXvHxh6dLx3wh9gj8kSAUpga11krTrbfF5XYsC"
light_token_minter = "3EPJBoxM8Evtv3Wk7R2mSWsrSzUD7WSKAaYugLgpCitV"
swap_example = "AsGVFxWqEn8icRBFQApxJe68x3r9zvfSbmiEzYFATGYn"
counter = "PDAm7XVHEkBvzBYDh8qF3z8NxnYQzPjGQJKcHVmMZpT"

[registry]
url = "https://api.apr.dev"

[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"

[scripts]
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
Loading