Skip to content
Draft
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
141 changes: 141 additions & 0 deletions docs/LIGHT_COMPRESSION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# Light Protocol Compression Integration

## Fork Changes

This fork adds compressed token support to anchor-spl's `token_interface`:

```rust
// anchor-spl/src/token_interface.rs
pub const COMPRESSED_TOKEN_ID: Pubkey = ...;
static IDS: [Pubkey; 3] = [spl_token::ID, spl_token_2022::ID, COMPRESSED_TOKEN_ID];
```

## TS SDK

The TS SDK adds `decompressIfNeeded()` to method builders:

```typescript
await program.methods
.swap(amount)
.decompressIfNeeded()
.rpc();
```

## Rust Macros (light-sdk-macros)

### `#[add_compressible_instructions]`

Generates compress/decompress instructions for listed accounts.

```rust
#[add_compressible_instructions(
PoolState = (POOL_SEED, ctx.accounts.amm_config, ctx.accounts.token_0_mint, ctx.accounts.token_1_mint),
)]
#[program]
pub mod my_program { ... }
```

### `#[derive(LightFinalize)]` + `#[compressible]`

Auto-compresses PDAs at instruction end.

```rust
#[derive(Accounts, LightFinalize)]
#[instruction(params: MyParams)]
pub struct MyInstruction<'info> {
#[account(mut)]
pub creator: Signer<'info>,

#[account(init, payer = creator, space = 8 + MyAccount::INIT_SPACE, seeds = [...], bump)]
#[compressible(
address_tree_info = params.address_tree_info,
output_tree = params.output_state_tree_index
)]
pub my_account: Account<'info, MyAccount>,

pub compression_config: AccountInfo<'info>,
}
```

### `#[light_instruction(params)]`

Auto-calls `light_finalize()` at instruction end.

```rust
#[light_instruction(params)]
pub fn create_account(ctx: Context<MyInstruction>, params: MyParams) -> Result<()> {
// your logic
Ok(()) // light_finalize auto-called here
}
```

### `#[light_mint]` (single mint)

Creates a compressed mint at instruction end.

```rust
#[derive(Accounts, LightFinalize)]
#[instruction(params: MyParams)]
pub struct CreateMint<'info> {
#[account(mut)]
pub creator: Signer<'info>,

/// CHECK: Mint signer PDA that seeds the mint address
pub mint_signer: UncheckedAccount<'info>,

/// CHECK: The mint account to be created
#[account(mut)]
#[light_mint(
mint_signer = mint_signer,
authority = authority,
decimals = 9,
address_tree_info = params.mint_address_tree_info,
output_tree = params.output_state_tree_index
)]
pub my_mint: UncheckedAccount<'info>,

pub authority: UncheckedAccount<'info>,
pub compression_config: AccountInfo<'info>,
}
```

## Required Program Setup

```rust
use light_sdk_types::CpiSigner;
use light_macros::derive_light_cpi_signer;

pub const LIGHT_CPI_SIGNER: CpiSigner = derive_light_cpi_signer!("YOUR_PROGRAM_ID");
```

## Params Struct Requirements

Your params struct (first arg of `#[instruction(...)]`) must have:

```rust
pub struct MyParams {
pub address_tree_info: PackedAddressTreeInfo, // for each compressible/mint
pub output_state_tree_index: u8,
pub proof: ValidityProof, // required for mint creation
}
```

## Current Limitations

- **Multiple mints**: Not supported via macro. Use `light_ctoken_sdk::ctoken::CreateCMintCpi` directly with CPI context batching.
- **Mixed PDAs + mints**: Not supported via macro. Handle mints manually.
- **Token accounts**: Use `light_ctoken_sdk` directly (`CreateCTokenAccountCpi`, `CTokenMintToCpi`).

## Gotchas

1. **Fee payer field**: Must be named `fee_payer`, `payer`, or `creator`
2. **compression_config field**: Required as `AccountInfo<'info>`
3. **remaining_accounts**: Light system accounts come via remaining_accounts - client must pass them in v2 format
4. **PackedAddressTreeInfo fields**: Uses `address_merkle_tree_pubkey_index`, `address_queue_pubkey_index`, `root_index`

## raydium-cp-swap Usage

Currently uses:
- `#[compressible]` on `pool_state` for auto-compression
- Manual `CreateCTokenAccountCpi` for token vaults
- Manual mint handling (LP mint created by client, no `#[light_mint]`)
2 changes: 2 additions & 0 deletions idl/spec/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ pub struct IdlInstructionAccount {
pub signer: bool,
#[serde(default, skip_serializing_if = "is_default")]
pub optional: bool,
#[serde(default, skip_serializing_if = "is_default")]
pub compressible: bool,
#[serde(skip_serializing_if = "is_default")]
pub address: Option<String>,
#[serde(skip_serializing_if = "is_default")]
Expand Down
1 change: 1 addition & 0 deletions idl/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,7 @@ mod legacy {
writable: acc.is_mut,
signer: acc.is_signer,
optional: acc.is_optional.unwrap_or_default(),
compressible: false,
address: Default::default(),
pda: acc
.pda
Expand Down
7 changes: 6 additions & 1 deletion spl/src/token_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ pub use crate::token_2022::*;
#[cfg(feature = "token_2022_extensions")]
pub use crate::token_2022_extensions::*;

static IDS: [Pubkey; 2] = [spl_token::ID, spl_token_2022::ID];
pub const COMPRESSED_TOKEN_ID: Pubkey = Pubkey::new_from_array([
9, 21, 163, 87, 35, 121, 78, 143, 182, 93, 7, 91, 107, 114, 105, 156, 56, 221, 2, 229, 148,
139, 117, 176, 229, 160, 65, 142, 128, 151, 91, 68,
]);

static IDS: [Pubkey; 3] = [spl_token::ID, spl_token_2022::ID, COMPRESSED_TOKEN_ID];

#[derive(Clone, Debug, Default, PartialEq, Copy)]
pub struct TokenAccount(spl_token_2022::state::Account);
Expand Down
5 changes: 4 additions & 1 deletion ts/packages/anchor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,11 @@
"dependencies": {
"@coral-xyz/anchor-errors": "^0.31.1",
"@coral-xyz/borsh": "^0.31.1",
"@lightprotocol/compressed-token": "file:../../../../light-protocol/js/compressed-token",
"@lightprotocol/stateless.js": "file:../../../../light-protocol/js/stateless.js",
"@noble/hashes": "^1.3.1",
"@solana/web3.js": "^1.69.0",
"@solana/spl-token": ">=0.3.9",
"@solana/web3.js": "^1.95.3",
"bn.js": "^5.1.2",
"bs58": "^4.0.1",
"buffer-layout": "^1.2.2",
Expand Down
12 changes: 11 additions & 1 deletion ts/packages/anchor/src/program/namespace/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,20 @@ export default class NamespaceFactory {
? AccountFactory.build(idl, coder, programId, provider)
: ({} as AccountNamespace<IDL>);

// First pass: build all instruction functions
const allInstructionFns: Record<string, any> = {};
idl.instructions.forEach((idlIx) => {
const ixItem = InstructionFactory.build<IDL, typeof idlIx>(
idlIx,
(ixName, ix) => coder.instruction.encode(ixName, ix),
programId
);
allInstructionFns[idlIx.name] = ixItem;
});

// Second pass: build all other namespaces with access to all instruction functions
idl.instructions.forEach((idlIx) => {
const ixItem = allInstructionFns[idlIx.name];
const txItem = TransactionFactory.build(idlIx, ixItem);
const rpcItem = RpcFactory.build(idlIx, txItem, idlErrors, provider);
const simulateItem = SimulateFactory.build(
Expand All @@ -85,7 +93,9 @@ export default class NamespaceFactory {
viewItem,
account,
idl.types || [],
getCustomResolver?.(idlIx)
getCustomResolver?.(idlIx),
idl,
allInstructionFns
);
const name = idlIx.name;

Expand Down
Loading