- Mint accounts uniquely represent a token on Solana and store its global metadata.
- Light mints are on-chain accounts like SPL mints, but the light token program sponsors the rent-exemption cost for you.
- Add an interface PDA to existing SPL or Token 2022 mints for interoperability with Light Token. The interface PDA holds SPL or Token 2022 tokens when wrapped to Light Token.
Light Rent Config Explained
Light Rent Config Explained
- A rent sponsor PDA by Light Protocol pays the rent-exemption cost for the account.
- Transaction fee payers bump a virtual rent balance when writing to the account, which keeps the account “hot”.
- “Cold” accounts virtual rent balance below threshold (eg 24h without write bump) get auto-compressed.
- The cold account’s state is cryptographically preserved on the Solana ledger. Users can load a cold account into hot state in-flight when using the account again.
Agent skill
Agent skill
Install the agent skill:See the AI tools guide for dedicated skills.
Report incorrect code
Copy
Ask AI
npx skills add https://zkcompression.com
- TypeScript Client
- Rust Client
- Program
createMintInterface is a unified interface that dispatches to different mint creation paths based on programId:TOKEN_PROGRAM_IDorTOKEN_2022_PROGRAM_ID→ delegates to SPL or T22createMint- Otherwise it defaults to
LIGHT_TOKEN_PROGRAM_ID→ creates a Light Token mint
Find the source code
here.
- Guide
- AI Prompt
Create Mint with Token Metadata
Installation
Installation
- npm
- yarn
- pnpm
Install packages in your working directory:Install the CLI globally:
Report incorrect code
Copy
Ask AI
npm install @lightprotocol/stateless.js@beta \
@lightprotocol/compressed-token@beta
Report incorrect code
Copy
Ask AI
npm install -g @lightprotocol/zk-compression-cli@beta
Install packages in your working directory:Install the CLI globally:
Report incorrect code
Copy
Ask AI
yarn add @lightprotocol/stateless.js@beta \
@lightprotocol/compressed-token@beta
Report incorrect code
Copy
Ask AI
yarn global add @lightprotocol/zk-compression-cli@beta
Install packages in your working directory:Install the CLI globally:
Report incorrect code
Copy
Ask AI
pnpm add @lightprotocol/stateless.js@beta \
@lightprotocol/compressed-token@beta
Report incorrect code
Copy
Ask AI
pnpm add -g @lightprotocol/zk-compression-cli@beta
- Localnet
- Devnet
Report incorrect code
Copy
Ask AI
# start local test-validator in a separate terminal
light test-validator
In the code examples, use
createRpc() without arguments for localnet.Get an API key from Helius and add to
.env:.env
Report incorrect code
Copy
Ask AI
API_KEY=<your-helius-api-key>
In the code examples, use
createRpc(RPC_URL) with the devnet URL.The
mintAuthority must be a Signer for light-mints but can be just a
PublicKey for SPL/T22.- Action
- Instruction
Report incorrect code
Copy
Ask AI
import "dotenv/config";
import { Keypair } from "@solana/web3.js";
import { createRpc } from "@lightprotocol/stateless.js";
import { createMintInterface, createTokenMetadata } from "@lightprotocol/compressed-token";
import { homedir } from "os";
import { readFileSync } from "fs";
// devnet:
const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`;
const rpc = createRpc(RPC_URL);
// localnet:
// const rpc = createRpc();
const payer = Keypair.fromSecretKey(
new Uint8Array(
JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8"))
)
);
(async function () {
const { mint, transactionSignature } = await createMintInterface(
rpc,
payer,
payer,
null,
9,
undefined,
undefined,
undefined,
createTokenMetadata("Example Token", "EXT", "https://example.com/metadata.json")
);
console.log("Mint:", mint.toBase58());
console.log("Tx:", transactionSignature);
})();
Report incorrect code
Copy
Ask AI
import "dotenv/config";
import {
Keypair,
ComputeBudgetProgram,
PublicKey,
Transaction,
sendAndConfirmTransaction,
} from "@solana/web3.js";
import {
createRpc,
getBatchAddressTreeInfo,
selectStateTreeInfo,
CTOKEN_PROGRAM_ID,
} from "@lightprotocol/stateless.js";
import {
createMintInstruction,
createTokenMetadata,
} from "@lightprotocol/compressed-token";
import { homedir } from "os";
import { readFileSync } from "fs";
const COMPRESSED_MINT_SEED = Buffer.from("compressed_mint");
function findMintAddress(mintSigner: PublicKey): [PublicKey, number] {
return PublicKey.findProgramAddressSync(
[COMPRESSED_MINT_SEED, mintSigner.toBuffer()],
CTOKEN_PROGRAM_ID,
);
}
// devnet:
// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`;
// const rpc = createRpc(RPC_URL);
// localnet:
const rpc = createRpc();
const payer = Keypair.fromSecretKey(
new Uint8Array(
JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")),
),
);
(async function () {
const mintSigner = Keypair.generate();
const addressTreeInfo = getBatchAddressTreeInfo();
const stateTreeInfo = selectStateTreeInfo(await rpc.getStateTreeInfos());
const [mintPda] = findMintAddress(mintSigner.publicKey);
const validityProof = await rpc.getValidityProofV2(
[],
[{ address: mintPda.toBytes(), treeInfo: addressTreeInfo }],
);
const ix = createMintInstruction(
mintSigner.publicKey,
9,
payer.publicKey,
null,
payer.publicKey,
validityProof,
addressTreeInfo,
stateTreeInfo,
createTokenMetadata(
"Example Token",
"EXT",
"https://example.com/metadata.json",
),
);
const tx = new Transaction().add(
ComputeBudgetProgram.setComputeUnitLimit({ units: 1_000_000 }),
ix,
);
const signature = await sendAndConfirmTransaction(rpc, tx, [
payer,
mintSigner,
]);
console.log("Mint:", mintPda.toBase58());
console.log("Tx:", signature);
})();
Add rent-free mint with token metadata
Report incorrect code
Copy
Ask AI
---
description: Add rent-free mint with token metadata
allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion, Task, TaskCreate, TaskGet, TaskList, TaskUpdate, TaskOutput, mcp__deepwiki, mcp__zkcompression
---
## Add rent-free mint with token metadata
Context:
- Guide: https://zkcompression.com/light-token/cookbook/create-mint
- Skills and resources index: https://zkcompression.com/skill.md
- SPL to Light reference: https://zkcompression.com/api-reference/solana-to-light-comparison
- Packages: @lightprotocol/compressed-token, @lightprotocol/stateless.js
SPL equivalent: createMint() → Light Token: createMintInterface()
### 1. Index project
- Grep `@solana/spl-token|Connection|Keypair|createMint` across src/
- Glob `**/*.ts` for project structure
- Identify: RPC setup, existing mint logic, entry point for mint creation
- Task subagent (Grep/Read/WebFetch) if project has multiple packages to scan in parallel
### 2. Read references
- WebFetch the guide above — follow the TypeScript Client tab
- WebFetch skill.md — check for a dedicated skill and resources matching this task
- TaskCreate one todo per phase below to track progress
### 3. Clarify intention
- AskUserQuestion: what is the goal? (new feature, migrate existing SPL code, add alongside existing)
- AskUserQuestion: does the project already have mint operations to extend, or is this greenfield?
- Summarize findings and wait for user confirmation before implementing
### 4. Create plan
- Based on steps 1–3, draft an implementation plan: which files to modify, what code to add, dependency changes
- Verify existing connection/signer setup is compatible with the cookbook prerequisites
- If anything is unclear or ambiguous, loop back to step 3 (AskUserQuestion)
- Present the plan to the user for approval before proceeding
### 5. Implement
- Add deps if missing: Bash `npm install @lightprotocol/compressed-token @lightprotocol/stateless.js`
- Follow the cookbook guide and the approved plan
- Write/Edit to create or modify files
- TaskUpdate to mark each step done
### 6. Verify
- Bash `tsc --noEmit`
- Bash run existing test suite if present
- TaskUpdate to mark complete
### Tools
- mcp__zkcompression__SearchLightProtocol("<query>") for API details
- mcp__deepwiki__ask_question("Lightprotocol/light-protocol", "<q>") for architecture
- Task subagent with Grep/Read/WebFetch for parallel lookups
- TaskList to check remaining work
Add Interface PDA to SPL / Token 2022 mints
- Add to Existing Mints
- Create SPL Mint with Interface PDA
- Create Token 2022 with Interface PDA
Register an interface PDA for an existing SPL mint for interoperability with Light Token. No mint authority required.
- Action
- Instruction
Report incorrect code
Copy
Ask AI
import "dotenv/config";
import { Keypair, PublicKey } from "@solana/web3.js";
import { createRpc } from "@lightprotocol/stateless.js";
import { createSplInterface } from "@lightprotocol/compressed-token";
import { homedir } from "os";
import { readFileSync } from "fs";
// devnet:
const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`;
const rpc = createRpc(RPC_URL);
// localnet:
// const rpc = createRpc();
const payer = Keypair.fromSecretKey(
new Uint8Array(
JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8"))
)
);
(async function () {
const existingMint = new PublicKey("YOUR_EXISTING_MINT_ADDRESS");
const tx = await createSplInterface(rpc, payer, existingMint);
console.log("Mint:", existingMint.toBase58());
console.log("Tx:", tx);
})();
Report incorrect code
Copy
Ask AI
import "dotenv/config";
import {
Keypair,
PublicKey,
Transaction,
sendAndConfirmTransaction,
} from "@solana/web3.js";
import { createRpc } from "@lightprotocol/stateless.js";
import { LightTokenProgram } from "@lightprotocol/compressed-token";
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
import { homedir } from "os";
import { readFileSync } from "fs";
// devnet:
const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`;
const rpc = createRpc(RPC_URL);
// localnet:
// const rpc = createRpc();
const payer = Keypair.fromSecretKey(
new Uint8Array(
JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8"))
)
);
(async function () {
const existingMint = new PublicKey("YOUR_EXISTING_MINT_ADDRESS");
const ix = await LightTokenProgram.createSplInterface({
feePayer: payer.publicKey,
mint: existingMint,
tokenProgramId: TOKEN_PROGRAM_ID,
});
const tx = new Transaction().add(ix);
const signature = await sendAndConfirmTransaction(rpc, tx, [payer]);
console.log("Mint:", existingMint.toBase58());
console.log("Tx:", signature);
})();
Pass
TOKEN_PROGRAM_ID to create a standard SPL mint with an interface PDA in one transaction for interoperability with Light Token.- Action
- Instruction
Report incorrect code
Copy
Ask AI
import "dotenv/config";
import { Keypair } from "@solana/web3.js";
import { createRpc } from "@lightprotocol/stateless.js";
import { createMintInterface } from "@lightprotocol/compressed-token";
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
import { homedir } from "os";
import { readFileSync } from "fs";
// devnet:
const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`;
const rpc = createRpc(RPC_URL);
// localnet:
// const rpc = createRpc();
const payer = Keypair.fromSecretKey(
new Uint8Array(
JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8"))
)
);
(async function () {
// Creates SPL mint + SPL Interface PDA in one transaction
const mintKeypair = Keypair.generate();
const { mint, transactionSignature } = await createMintInterface(
rpc,
payer,
payer,
null,
9,
mintKeypair,
undefined,
TOKEN_PROGRAM_ID
);
console.log("Mint:", mint.toBase58());
console.log("Tx:", transactionSignature);
})();
Report incorrect code
Copy
Ask AI
import "dotenv/config";
import {
Keypair,
SystemProgram,
Transaction,
sendAndConfirmTransaction,
} from "@solana/web3.js";
import { createRpc } from "@lightprotocol/stateless.js";
import { LightTokenProgram } from "@lightprotocol/compressed-token";
import {
MINT_SIZE,
TOKEN_PROGRAM_ID,
createInitializeMint2Instruction,
} from "@solana/spl-token";
import { homedir } from "os";
import { readFileSync } from "fs";
// devnet:
const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`;
const rpc = createRpc(RPC_URL);
// localnet:
// const rpc = createRpc();
const payer = Keypair.fromSecretKey(
new Uint8Array(
JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8"))
)
);
(async function () {
const mintKeypair = Keypair.generate();
const decimals = 9;
// Get rent for mint account
const rentExemptBalance = await rpc.getMinimumBalanceForRentExemption(MINT_SIZE);
// Instruction 1: Create mint account
const createMintAccountIx = SystemProgram.createAccount({
fromPubkey: payer.publicKey,
lamports: rentExemptBalance,
newAccountPubkey: mintKeypair.publicKey,
programId: TOKEN_PROGRAM_ID,
space: MINT_SIZE,
});
// Instruction 2: Initialize mint
const initializeMintIx = createInitializeMint2Instruction(
mintKeypair.publicKey,
decimals,
payer.publicKey, // mint authority
null, // freeze authority
TOKEN_PROGRAM_ID
);
// Instruction 3: Create SPL Interface PDA (enables compression for this mint)
const createSplInterfaceIx = await LightTokenProgram.createSplInterface({
feePayer: payer.publicKey,
mint: mintKeypair.publicKey,
tokenProgramId: TOKEN_PROGRAM_ID,
});
const tx = new Transaction().add(
createMintAccountIx,
initializeMintIx,
createSplInterfaceIx
);
const signature = await sendAndConfirmTransaction(rpc, tx, [
payer,
mintKeypair,
]);
console.log("Mint:", mintKeypair.publicKey.toBase58());
console.log("Tx:", signature);
})();
Pass
TOKEN_2022_PROGRAM_ID to create a Token-2022 mint with an interface PDA in one transaction for interoperability with Light Token.- Action
- Instruction
Report incorrect code
Copy
Ask AI
import "dotenv/config";
import { Keypair } from "@solana/web3.js";
import { createRpc } from "@lightprotocol/stateless.js";
import { createMintInterface } from "@lightprotocol/compressed-token";
import { TOKEN_2022_PROGRAM_ID } from "@solana/spl-token";
import { homedir } from "os";
import { readFileSync } from "fs";
// devnet:
const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`;
const rpc = createRpc(RPC_URL);
// localnet:
// const rpc = createRpc();
const payer = Keypair.fromSecretKey(
new Uint8Array(
JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8"))
)
);
(async function () {
// Creates Token-2022 mint and SPL interface PDA in one transaction
// SPL interface PDA holds Token-2022 tokens when wrapped to light-token
const mintKeypair = Keypair.generate();
const { mint, transactionSignature } = await createMintInterface(
rpc,
payer,
payer,
null,
9,
mintKeypair,
undefined,
TOKEN_2022_PROGRAM_ID
);
console.log("Mint:", mint.toBase58());
console.log("Tx:", transactionSignature);
})();
Report incorrect code
Copy
Ask AI
import "dotenv/config";
import {
Keypair,
SystemProgram,
Transaction,
sendAndConfirmTransaction,
} from "@solana/web3.js";
import { createRpc } from "@lightprotocol/stateless.js";
import { LightTokenProgram } from "@lightprotocol/compressed-token";
import {
MINT_SIZE,
TOKEN_2022_PROGRAM_ID,
createInitializeMint2Instruction,
} from "@solana/spl-token";
import { homedir } from "os";
import { readFileSync } from "fs";
// devnet:
const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`;
const rpc = createRpc(RPC_URL);
// localnet:
// const rpc = createRpc();
const payer = Keypair.fromSecretKey(
new Uint8Array(
JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8"))
)
);
(async function () {
const mintKeypair = Keypair.generate();
const decimals = 9;
// Get rent for mint account
const rentExemptBalance =
await rpc.getMinimumBalanceForRentExemption(MINT_SIZE);
// Instruction 1: Create mint account
const createMintAccountIx = SystemProgram.createAccount({
fromPubkey: payer.publicKey,
lamports: rentExemptBalance,
newAccountPubkey: mintKeypair.publicKey,
programId: TOKEN_2022_PROGRAM_ID,
space: MINT_SIZE,
});
// Instruction 2: Initialize mint
const initializeMintIx = createInitializeMint2Instruction(
mintKeypair.publicKey,
decimals,
payer.publicKey, // mint authority
null, // freeze authority
TOKEN_2022_PROGRAM_ID
);
// Instruction 3: Create SPL interface PDA
// Holds Token-2022 tokens when wrapped to light-token
const createSplInterfaceIx = await LightTokenProgram.createSplInterface({
feePayer: payer.publicKey,
mint: mintKeypair.publicKey,
tokenProgramId: TOKEN_2022_PROGRAM_ID,
});
const tx = new Transaction().add(
createMintAccountIx,
initializeMintIx,
createSplInterfaceIx
);
const signature = await sendAndConfirmTransaction(rpc, tx, [
payer,
mintKeypair,
]);
console.log("Mint:", mintKeypair.publicKey.toBase58());
console.log("Tx:", signature);
})();
CreateMint creates an on-chain mint account that can optionally include token metadata.
The instruction also writes a compressed mint address to the address Merkle tree, which preserves the mint state when the on-chain account is compressed.Compare to SPL:- Guide
- AI Prompt
Prerequisites
Dependencies
Dependencies
Cargo.toml
Report incorrect code
Copy
Ask AI
[dependencies]
light-token = "0.4.0"
light-client = { version = "0.19.0", features = ["v2"] }
solana-sdk = "2"
borsh = "0.10.4"
tokio = { version = "1", features = ["full"] }
Developer Environment
Developer Environment
- In-Memory (LightProgramTest)
- Localnet (LightClient)
- Devnet (LightClient)
Test with Lite-SVM (…)
Report incorrect code
Copy
Ask AI
# Initialize project
cargo init my-light-project
cd my-light-project
# Run tests
cargo test
Report incorrect code
Copy
Ask AI
use light_program_test::{LightProgramTest, ProgramTestConfig};
use solana_sdk::signer::Signer;
#[tokio::test]
async fn test_example() {
// In-memory test environment
let mut rpc = LightProgramTest::new(ProgramTestConfig::default())
.await
.unwrap();
let payer = rpc.get_payer().insecure_clone();
println!("Payer: {}", payer.pubkey());
}
Connects to a local test validator.
- npm
- yarn
- pnpm
Report incorrect code
Copy
Ask AI
npm install -g @lightprotocol/zk-compression-cli@beta
Report incorrect code
Copy
Ask AI
yarn global add @lightprotocol/zk-compression-cli@beta
Report incorrect code
Copy
Ask AI
pnpm add -g @lightprotocol/zk-compression-cli@beta
Report incorrect code
Copy
Ask AI
# Initialize project
cargo init my-light-project
cd my-light-project
# Start local test validator (in separate terminal)
light test-validator
Report incorrect code
Copy
Ask AI
use light_client::rpc::{LightClient, LightClientConfig, Rpc};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Connects to http://localhost:8899
let rpc = LightClient::new(LightClientConfig::local()).await?;
let slot = rpc.get_slot().await?;
println!("Current slot: {}", slot);
Ok(())
}
Replace
<your-api-key> with your actual API key. Get your API key here.Report incorrect code
Copy
Ask AI
use light_client::rpc::{LightClient, LightClientConfig, Rpc};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let rpc_url = "https://devnet.helius-rpc.com?api-key=<your_api_key>";
let rpc = LightClient::new(
LightClientConfig::new(rpc_url.to_string(), None, None)
).await?;
println!("Connected to Devnet");
Ok(())
}
Create Light Token Mint with Token Metadata
Find the source code here.
- Action
- Instruction
Report incorrect code
Copy
Ask AI
use light_client::rpc::Rpc;
use light_token_client::actions::{CreateMint, TokenMetadata};
use rust_client::setup_rpc_and_payer;
use solana_sdk::signer::Signer;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let (mut rpc, payer) = setup_rpc_and_payer().await;
let (signature, mint) = CreateMint {
decimals: 9,
freeze_authority: None,
token_metadata: Some(TokenMetadata {
name: "Example Token".to_string(),
symbol: "EXT".to_string(),
uri: "https://example.com/metadata.json".to_string(),
update_authority: Some(payer.pubkey()),
additional_metadata: Some(vec![("type".to_string(), "example".to_string())]),
}),
seed: None,
}
.execute(&mut rpc, &payer, &payer)
.await?;
let data = rpc.get_account(mint).await?;
println!("Mint: {mint} exists: {} Tx: {signature}", data.is_some());
Ok(())
}
Report incorrect code
Copy
Ask AI
use light_client::{
indexer::{AddressWithTree, Indexer},
rpc::Rpc,
};
use light_program_test::{LightProgramTest, ProgramTestConfig};
use light_token::instruction::{
derive_mint_compressed_address, find_mint_address, CreateMint, CreateMintParams,
DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP,
};
use light_token_interface::{
instructions::extensions::{
token_metadata::TokenMetadataInstructionData, ExtensionInstructionData,
},
state::AdditionalMetadata,
};
use solana_sdk::{signature::Keypair, signer::Signer};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut rpc = LightProgramTest::new(ProgramTestConfig::new_v2(false, None)).await?;
let payer = rpc.get_payer().insecure_clone();
let mint_seed = Keypair::new();
let decimals = 9u8;
// Get address tree to store compressed address for when mint turns inactive
// We must create a compressed address at creation to ensure the mint does not exist yet
let address_tree = rpc.get_address_tree_v2();
// Get state tree to store mint when inactive
let output_queue = rpc.get_random_state_tree_info().unwrap().queue;
// Derive mint addresses
let compression_address =
derive_mint_compressed_address(&mint_seed.pubkey(), &address_tree.tree);
let mint = find_mint_address(&mint_seed.pubkey()).0; // on-chain Mint PDA
// Fetch validity proof to proof address does not exist yet
let rpc_result = rpc
.get_validity_proof(
vec![],
vec![AddressWithTree {
address: compression_address,
tree: address_tree.tree,
}],
None,
)
.await
.unwrap()
.value;
// Build CreateMintParams with token metadata extension
let params = CreateMintParams {
decimals,
address_merkle_tree_root_index: rpc_result.addresses[0].root_index, // stores mint compressed address
mint_authority: payer.pubkey(),
proof: rpc_result.proof.0.unwrap(),
compression_address, // address for compression when mint turns inactive
mint,
bump: find_mint_address(&mint_seed.pubkey()).1,
freeze_authority: None,
extensions: Some(vec![ExtensionInstructionData::TokenMetadata(
TokenMetadataInstructionData {
update_authority: Some(payer.pubkey().to_bytes().into()),
name: b"Example Token".to_vec(),
symbol: b"EXT".to_vec(),
uri: b"https://example.com/metadata.json".to_vec(),
additional_metadata: Some(vec![AdditionalMetadata {
key: b"type".to_vec(),
value: b"example".to_vec(),
}]),
},
)]),
rent_payment: DEFAULT_RENT_PAYMENT, // 24h of rent
write_top_up: DEFAULT_WRITE_TOP_UP, // 3h of rent
};
// Build and send instruction (mint_seed must sign)
let create_mint_instruction = CreateMint::new(
params,
mint_seed.pubkey(),
payer.pubkey(),
address_tree.tree,
output_queue,
)
.instruction()?;
let sig = rpc
.create_and_send_transaction(&[create_mint_instruction], &payer.pubkey(), &[&payer, &mint_seed])
.await?;
let data = rpc.get_account(mint).await?;
println!("Mint: {} exists: {} Tx: {sig}", mint, data.is_some());
Ok(())
}
Create rent-free mint with token metadata
Report incorrect code
Copy
Ask AI
---
description: Create rent-free mint with token metadata
allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion, Task, TaskCreate, TaskGet, TaskList, TaskUpdate, TaskOutput, mcp__deepwiki, mcp__zkcompression
---
## Create rent-free mint with token metadata
Context:
- Guide: https://zkcompression.com/light-token/cookbook/create-mint
- Skills and resources index: https://zkcompression.com/skill.md
- SPL to Light reference: https://zkcompression.com/api-reference/solana-to-light-comparison
- Crates: light-token-client (actions), light-token (instructions), light-client (RPC)
SPL equivalent: spl_token::instruction::initialize_mint → Light Token: CreateMint
### 1. Index project
- Grep `light_token::|light_token_client::|solana_sdk|Keypair|async|CreateMint|initialize_mint` across src/
- Glob `**/*.rs` for project structure
- Identify: RPC setup, existing token ops, entry point for mint creation
- Check Cargo.toml for existing light-* dependencies and solana-sdk version
- Task subagent (Grep/Read/WebFetch) if project has multiple crates to scan in parallel
### 2. Read references
- WebFetch the guide above — follow the Rust Client tab
- WebFetch skill.md — check for a dedicated skill and resources matching this task
- TaskCreate one todo per phase below to track progress
### 3. Clarify intention
- AskUserQuestion: what is the goal? (new feature, migrate existing SPL code, add alongside existing)
- AskUserQuestion: does the project already have mint operations to extend, or is this greenfield?
- AskUserQuestion: action-level API (high-level, fewer lines) or instruction-level API (low-level, full control)?
- Summarize findings and wait for user confirmation before implementing
### 4. Create plan
- Based on steps 1–3, draft an implementation plan: which files to modify, what code to add, dependency changes
- Verify existing Rpc/signer setup is compatible with the cookbook prerequisites (light_client::rpc::Rpc, solana_sdk::signature::Keypair)
- If anything is unclear or ambiguous, loop back to step 3 (AskUserQuestion)
- Present the plan to the user for approval before proceeding
### 5. Implement
- Add deps if missing: Bash `cargo add light-token-client light-token light-client --features light-client/v2`
- Follow the cookbook guide and the approved plan
- Write/Edit to create or modify files
- TaskUpdate to mark each step done
### 6. Verify
- Bash `cargo check`
- Bash `cargo test` if tests exist
- TaskUpdate to mark complete
### Tools
- mcp__zkcompression__SearchLightProtocol("<query>") for API details
- mcp__deepwiki__ask_question("Lightprotocol/light-protocol", "<q>") for architecture
- Task subagent with Grep/Read/WebFetch for parallel lookups
- TaskList to check remaining work
- CPI
- Anchor Macros
- Guide
- AI Prompt
Compare to SPL:
- Mint
- With Token Metadata
Configure Token Metadata
Report incorrect code
Copy
Ask AI
use light_token_interface::instructions::extensions::{
token_metadata::TokenMetadataInstructionData, ExtensionInstructionData,
};
let extensions = Some(vec![ExtensionInstructionData::TokenMetadata(
TokenMetadataInstructionData {
update_authority: Some(authority.key.to_bytes().into()),
name: b"Example Token".to_vec(),
symbol: b"EXT".to_vec(),
uri: b"https://example.com/metadata.json".to_vec(),
additional_metadata: None,
},
)]);
Fields must be set at light-mint creation. Standard fields (
name,
symbol, uri) can be updated by update_authority. For
additional_metadata, only existing keys can be modified or removed. New keys
cannot be added after creation.Configure Mint
Configure mint parameters includingdecimals, authorities, rent settings, and pass extensions from the previous step.Report incorrect code
Copy
Ask AI
use light_token::instruction::CreateMintParams;
let params = CreateMintParams {
decimals,
address_merkle_tree_root_index,
mint_authority: *ctx.accounts.authority.key,
proof,
compression_address,
mint,
bump,
freeze_authority,
extensions,
rent_payment: rent_payment.unwrap_or(16), // ~24 hours rent
write_top_up: write_top_up.unwrap_or(766), // ~3 hours rent
};
The address of the mint account is stored in an address Merkle tree
, which is maintained by the protocol.
The client passes a validity proof that proves the mint address does not
exist yet.
System Accounts
Include system accounts such as the Light System Program to verify the proof and write the mint address to the address tree.System Accounts List
System Accounts List
| Account | Description | |
|---|---|---|
| 1 | Verifies validity proofs and executes compressed account state transitions. | |
| 2 | CPI Authority PDA | PDA that authorizes CPIs from the Light Token Program to the Light System Program. |
| 3 | Registered Program PDA | Proves the Light Token Program is registered to use compression. |
| 4 | Signs CPI calls from the Light System Program to the Account Compression Program. | |
| 5 | Writes to state and address Merkle tree accounts. | |
| 6 | Solana System Program. |
Report incorrect code
Copy
Ask AI
use light_token::instruction::SystemAccountInfos;
let system_accounts = SystemAccountInfos {
light_system_program: ctx.accounts.light_system_program.to_account_info(),
cpi_authority_pda: ctx.accounts.cpi_authority_pda.to_account_info(),
registered_program_pda: ctx.accounts.registered_program_pda.to_account_info(),
account_compression_authority: ctx.accounts.account_compression_authority.to_account_info(),
account_compression_program: ctx.accounts.account_compression_program.to_account_info(),
system_program: ctx.accounts.system_program.to_account_info(),
};
Build Account Infos and CPI the Light Token Program
Useinvoke or invoke_signed:- When
mint_seedis an external keypair, useinvoke. - When
mint_seedis a PDA, useinvoke_signedwith its seeds. - When both
mint_seedandauthorityare PDAs, useinvoke_signedwith both seeds.
- invoke (External signer)
- invoke_signed (PDA mint seed)
- invoke_signed (Two PDA signers)
Report incorrect code
Copy
Ask AI
use light_token::instruction::CreateMintCpi;
CreateMintCpi::new(
mint_seed.clone(),
authority.clone(),
payer.clone(),
address_tree.clone(), // stores address
output_queue.clone(), // stores account when inactive
compressible_config.clone(), // rent settings
mint.clone(),
rent_sponsor.clone(),
system_accounts,
params,
)
.invoke()
Report incorrect code
Copy
Ask AI
use light_token::instruction::CreateMintCpi;
let signer_seeds: &[&[u8]] = &[MINT_SIGNER_SEED, &[bump]];
CreateMintCpi::new(
mint_seed.clone(),
authority.clone(),
payer.clone(),
address_tree.clone(),
output_queue.clone(),
compressible_config.clone(),
mint.clone(),
rent_sponsor.clone(),
system_accounts,
params,
)
.invoke_signed(&[signer_seeds])
Report incorrect code
Copy
Ask AI
use light_token::instruction::CreateMintCpi;
let mint_seed_seeds: &[&[u8]] = &[MINT_SIGNER_SEED, &[mint_seed_bump]];
let authority_seeds: &[&[u8]] = &[MINT_AUTHORITY_SEED, &[authority_bump]];
CreateMintCpi::new(
mint_seed.clone(),
authority.clone(),
payer.clone(),
address_tree.clone(),
output_queue.clone(),
compressible_config.clone(),
mint.clone(),
rent_sponsor.clone(),
system_accounts,
params,
)
.invoke_signed(&[mint_seed_seeds, authority_seeds])
Full Code Example
- Anchor
View the source code and full example with shared test utilities.
Report incorrect code
Copy
Ask AI
#![allow(unexpected_cfgs, deprecated)]
use anchor_lang::prelude::*;
use light_token::instruction::{
CreateMintCpi, CreateMintParams, SystemAccountInfos, DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP,
};
use light_token::{CompressedProof, ExtensionInstructionData, TokenMetadataInstructionData};
declare_id!("A1rJEoepgKYWZYZ8KVFpxgeeRGwBrU7xk8S39srjVkUX");
/// Token metadata parameters for creating a mint with metadata.
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct TokenMetadataParams {
pub name: Vec<u8>,
pub symbol: Vec<u8>,
pub uri: Vec<u8>,
pub update_authority: Option<Pubkey>,
}
#[program]
pub mod light_token_anchor_create_mint {
use super::*;
pub fn create_mint(
ctx: Context<CreateMintAccounts>,
decimals: u8,
address_merkle_tree_root_index: u16,
compression_address: [u8; 32],
proof: CompressedProof,
freeze_authority: Option<Pubkey>,
bump: u8,
rent_payment: Option<u8>,
write_top_up: Option<u32>,
metadata: Option<TokenMetadataParams>,
) -> Result<()> {
let mint = light_token::instruction::find_mint_address(ctx.accounts.mint_seed.key).0;
let extensions = metadata.map(|m| {
vec![ExtensionInstructionData::TokenMetadata(
TokenMetadataInstructionData {
update_authority: m
.update_authority
.map(|p| p.to_bytes().into()),
name: m.name,
symbol: m.symbol,
uri: m.uri,
additional_metadata: None,
},
)]
});
let params = CreateMintParams {
decimals,
address_merkle_tree_root_index,
mint_authority: *ctx.accounts.authority.key,
proof,
compression_address,
mint,
bump,
freeze_authority,
extensions,
rent_payment: rent_payment.unwrap_or(DEFAULT_RENT_PAYMENT),
write_top_up: write_top_up.unwrap_or(DEFAULT_WRITE_TOP_UP),
};
let system_accounts = SystemAccountInfos {
light_system_program: ctx.accounts.light_system_program.to_account_info(),
cpi_authority_pda: ctx.accounts.cpi_authority_pda.to_account_info(),
registered_program_pda: ctx.accounts.registered_program_pda.to_account_info(),
account_compression_authority: ctx.accounts.account_compression_authority.to_account_info(),
account_compression_program: ctx.accounts.account_compression_program.to_account_info(),
system_program: ctx.accounts.system_program.to_account_info(),
};
CreateMintCpi {
mint_seed: ctx.accounts.mint_seed.to_account_info(),
authority: ctx.accounts.authority.to_account_info(),
payer: ctx.accounts.payer.to_account_info(),
address_tree: ctx.accounts.address_tree.to_account_info(),
output_queue: ctx.accounts.output_queue.to_account_info(),
compressible_config: ctx.accounts.compressible_config.to_account_info(),
mint: ctx.accounts.mint.to_account_info(),
rent_sponsor: ctx.accounts.rent_sponsor.to_account_info(),
system_accounts,
cpi_context: None,
cpi_context_account: None,
params,
}
.invoke()?;
Ok(())
}
}
#[derive(Accounts)]
pub struct CreateMintAccounts<'info> {
/// CHECK: Light token program for CPI
pub light_token_program: AccountInfo<'info>,
pub mint_seed: Signer<'info>,
/// CHECK: Validated by light-token CPI
pub authority: AccountInfo<'info>,
#[account(mut)]
pub payer: Signer<'info>,
/// CHECK: Validated by light-token CPI
#[account(mut)]
pub address_tree: AccountInfo<'info>,
/// CHECK: Validated by light-token CPI
#[account(mut)]
pub output_queue: AccountInfo<'info>,
/// CHECK: Validated by light-token CPI
pub light_system_program: AccountInfo<'info>,
/// CHECK: Validated by light-token CPI
pub cpi_authority_pda: AccountInfo<'info>,
/// CHECK: Validated by light-token CPI
pub registered_program_pda: AccountInfo<'info>,
/// CHECK: Validated by light-token CPI
pub account_compression_authority: AccountInfo<'info>,
/// CHECK: Validated by light-token CPI
pub account_compression_program: AccountInfo<'info>,
pub system_program: Program<'info, System>,
/// CHECK: Validated by light-token CPI - use light_token::token::config_pda()
pub compressible_config: AccountInfo<'info>,
/// CHECK: Validated by light-token CPI - derived from find_mint_address(mint_seed)
#[account(mut)]
pub mint: AccountInfo<'info>,
/// CHECK: Validated by light-token CPI - use light_token::token::rent_sponsor_pda()
#[account(mut)]
pub rent_sponsor: AccountInfo<'info>,
}
Add create-mint CPI to an Anchor program
Report incorrect code
Copy
Ask AI
---
description: Add create-mint CPI to an Anchor program
allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion, Task, TaskCreate, TaskGet, TaskList, TaskUpdate, TaskOutput, mcp__deepwiki, mcp__zkcompression
---
## Add create-mint CPI to an Anchor program
Context:
- Guide: https://zkcompression.com/light-token/cookbook/create-mint
- Skills and resources index: https://zkcompression.com/skill.md
- SPL to Light reference: https://zkcompression.com/api-reference/solana-to-light-comparison
- Crate: light-token (CreateMintCpi, CreateMintParams, SystemAccountInfos)
- Example: https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/create-mint
Key CPI struct: `light_token::instruction::CreateMintCpi`
### 1. Index project
- Grep `declare_id|#\[program\]|anchor_lang|Account<|Pubkey|invoke|mint|decimals|authority|metadata` across src/
- Glob `**/*.rs` and `**/Cargo.toml` for project structure
- Identify: program ID, existing instructions, account structs, mint seeds
- Read Cargo.toml — note existing dependencies and framework version
- Task subagent (Grep/Read/WebFetch) if project has multiple crates to scan in parallel
### 2. Read references
- WebFetch the guide above — review the CPI tab under Program: token metadata config, mint params, system accounts, invoke patterns
- WebFetch skill.md — check for a dedicated skill and resources matching this task
- TaskCreate one todo per phase below to track progress
### 3. Clarify intention
- AskUserQuestion: what is the goal? (add create-mint to existing program, new program from scratch, migrate from SPL create_mint)
- AskUserQuestion: should the mint seed be an external keypair or a PDA? (determines invoke vs invoke_signed)
- AskUserQuestion: do you need token metadata (name, symbol, uri)?
- Summarize findings and wait for user confirmation before implementing
### 4. Create plan
- Based on steps 1–3, draft an implementation plan: which files to modify, what code to add, dependency changes
- Follow the guide's step order: Configure Token Metadata → Configure Mint Params → System Accounts → Build CPI and invoke
- If anything is unclear or ambiguous, loop back to step 3 (AskUserQuestion)
- Present the plan to the user for approval before proceeding
### 5. Implement
- Add deps if missing: Bash `cargo add light-token anchor-lang@0.31`
- Follow the guide and the approved plan
- Write/Edit to create or modify files
- TaskUpdate to mark each step done
### 6. Verify
- Bash `anchor build`
- Bash `anchor test` if tests exist
- TaskUpdate to mark complete
### Tools
- mcp__zkcompression__SearchLightProtocol("<query>") for API details
- mcp__deepwiki__ask_question("Lightprotocol/light-protocol", "<q>") for architecture
- Task subagent with Grep/Read/WebFetch for parallel lookups
- TaskList to check remaining work
- Guide
- AI Prompt
Compare to SPL:
- Mint
- With Token Metadata
Dependencies
Report incorrect code
Copy
Ask AI
[dependencies]
light-sdk = { version = "0.18.0", features = ["anchor", "v2", "cpi-context"] }
light-sdk-macros = "0.18.0"
light-compressible = "0.1.0"
anchor-lang = "0.31"
Program
Add#[light_program] above #[program]:Report incorrect code
Copy
Ask AI
use light_sdk_macros::light_program;
#[light_program]
#[program]
pub mod my_program {
use super::*;
pub fn create_mint<'info>(
ctx: Context<'_, '_, '_, 'info, CreateMint<'info>>,
params: CreateMintParams,
) -> Result<()> {
Ok(())
}
}
Accounts struct
DeriveLightAccounts on your Accounts struct and add #[light_account(...)] next to #[account(...)].- Mint
- Mint with metadata
Report incorrect code
Copy
Ask AI
/// CHECK: Validated by light-token CPI
#[account(mut)]
#[light_account(init,
mint::signer = mint_signer,
mint::authority = fee_payer,
mint::decimals = 9,
mint::seeds = &[MINT_SIGNER_SEED, self.authority.to_account_info().key.as_ref()],
mint::bump = params.mint_signer_bump
)]
pub mint: UncheckedAccount<'info>,
Report incorrect code
Copy
Ask AI
/// CHECK: Validated by light-token CPI
#[account(mut)]
#[light_account(init,
mint::signer = mint_signer,
mint::authority = fee_payer,
mint::decimals = 9,
mint::seeds = &[MINT_SIGNER_SEED, self.authority.to_account_info().key.as_ref()],
mint::bump = params.mint_signer_bump,
mint::name = params.name.clone(),
mint::symbol = params.symbol.clone(),
mint::uri = params.uri.clone(),
mint::update_authority = authority,
mint::additional_metadata = params.additional_metadata.clone()
)]
pub mint: UncheckedAccount<'info>,
Full code example
View the source code and full example with shared test utilities.
Report incorrect code
Copy
Ask AI
#![allow(deprecated)]
use anchor_lang::prelude::*;
use light_compressible::CreateAccountsProof;
use light_sdk::derive_light_cpi_signer;
use light_sdk_macros::{light_program, LightAccounts};
use light_sdk_types::CpiSigner;
declare_id!("HVmVqSJyMejBeUigePMSfX4aENJzCGHNxAJuT2PDMPRx");
pub const LIGHT_CPI_SIGNER: CpiSigner =
derive_light_cpi_signer!("HVmVqSJyMejBeUigePMSfX4aENJzCGHNxAJuT2PDMPRx");
pub const MINT_SIGNER_SEED: &[u8] = b"mint_signer";
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct CreateMintParams {
pub create_accounts_proof: CreateAccountsProof,
pub mint_signer_bump: u8,
}
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct CreateMintWithMetadataParams {
pub create_accounts_proof: CreateAccountsProof,
pub mint_signer_bump: u8,
pub name: Vec<u8>,
pub symbol: Vec<u8>,
pub uri: Vec<u8>,
}
#[derive(Accounts, LightAccounts)]
#[instruction(params: CreateMintParams)]
pub struct CreateMint<'info> {
#[account(mut)]
pub fee_payer: Signer<'info>,
pub authority: Signer<'info>,
/// CHECK: PDA derived from authority
#[account(
seeds = [MINT_SIGNER_SEED, authority.key().as_ref()],
bump,
)]
pub mint_signer: UncheckedAccount<'info>,
/// CHECK: Initialized by light_mint CPI
#[account(mut)]
#[light_account(init,
mint::signer = mint_signer,
mint::authority = fee_payer,
mint::decimals = 9,
mint::seeds = &[MINT_SIGNER_SEED, self.authority.to_account_info().key.as_ref()],
mint::bump = params.mint_signer_bump
)]
pub mint: UncheckedAccount<'info>,
/// CHECK: Compression config PDA
pub compression_config: AccountInfo<'info>,
/// CHECK: Light Token compressible config
pub light_token_compressible_config: AccountInfo<'info>,
/// CHECK: Rent sponsor
#[account(mut)]
pub rent_sponsor: AccountInfo<'info>,
/// CHECK: Light Token program
pub light_token_program: AccountInfo<'info>,
/// CHECK: Light Token CPI authority
pub light_token_cpi_authority: AccountInfo<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts, LightAccounts)]
#[instruction(params: CreateMintWithMetadataParams)]
pub struct CreateMintWithMetadata<'info> {
#[account(mut)]
pub fee_payer: Signer<'info>,
pub authority: Signer<'info>,
/// CHECK: PDA derived from authority
#[account(
seeds = [MINT_SIGNER_SEED, authority.key().as_ref()],
bump,
)]
pub mint_signer: UncheckedAccount<'info>,
/// CHECK: Initialized by light_mint CPI
#[account(mut)]
#[light_account(init,
mint::signer = mint_signer,
mint::authority = fee_payer,
mint::decimals = 9,
mint::seeds = &[MINT_SIGNER_SEED, self.authority.to_account_info().key.as_ref()],
mint::bump = params.mint_signer_bump,
mint::name = params.name.clone(),
mint::symbol = params.symbol.clone(),
mint::uri = params.uri.clone(),
mint::update_authority = authority
)]
pub mint: UncheckedAccount<'info>,
/// CHECK: Compression config PDA
pub compression_config: AccountInfo<'info>,
/// CHECK: Light Token compressible config
pub light_token_compressible_config: AccountInfo<'info>,
/// CHECK: Rent sponsor
#[account(mut)]
pub rent_sponsor: AccountInfo<'info>,
/// CHECK: Light Token program
pub light_token_program: AccountInfo<'info>,
/// CHECK: Light Token CPI authority
pub light_token_cpi_authority: AccountInfo<'info>,
pub system_program: Program<'info, System>,
}
#[light_program]
#[program]
pub mod light_token_macro_create_mint {
use super::*;
#[allow(unused_variables)]
pub fn create_mint<'info>(
ctx: Context<'_, '_, '_, 'info, CreateMint<'info>>,
params: CreateMintParams,
) -> Result<()> {
Ok(())
}
#[allow(unused_variables)]
pub fn create_mint_with_metadata<'info>(
ctx: Context<'_, '_, '_, 'info, CreateMintWithMetadata<'info>>,
params: CreateMintWithMetadataParams,
) -> Result<()> {
Ok(())
}
}
Create a rent-free mint with Anchor macros
Report incorrect code
Copy
Ask AI
---
description: Create a rent-free mint with Anchor macros
allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion, Task, TaskCreate, TaskGet, TaskList, TaskUpdate, TaskOutput, mcp__deepwiki, mcp__zkcompression
---
## Create a rent-free mint with Anchor macros
Context:
- Guide: https://zkcompression.com/light-token/cookbook/create-mint
- Skills and resources index: https://zkcompression.com/skill.md
- SPL to Light reference: https://zkcompression.com/api-reference/solana-to-light-comparison
- Crates: light-sdk, light-sdk-macros, light-compressible, anchor-lang
- Example: https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/create-mint
Key macros: `#[light_program]`, `LightAccounts`, `#[light_account(init, mint::...)]`
### 1. Index project
- Grep `#\[program\]|anchor_lang|Account<|Accounts|seeds|init|payer|mint` across src/
- Glob `**/*.rs` and `**/Cargo.toml` for project structure
- Identify: existing program module, account structs, mint patterns, PDA seeds
- Read Cargo.toml — note existing dependencies and framework version
- Task subagent (Grep/Read/WebFetch) if project has multiple crates to scan in parallel
### 2. Read references
- WebFetch the guide above — review the Anchor Macros tab under Program
- WebFetch skill.md — check for a dedicated skill and resources matching this task
- TaskCreate one todo per phase below to track progress
### 3. Clarify intention
- AskUserQuestion: what is the goal? (new program from scratch, add rent-free mint to existing program, migrate from SPL create_mint)
- AskUserQuestion: do you need token metadata (name, symbol, uri)?
- Summarize findings and wait for user confirmation before implementing
### 4. Create plan
- Based on steps 1–3, draft an implementation plan
- Follow the guide's step order: Dependencies → Program Module (#[light_program]) → Accounts Struct (#[light_account(init, mint::...)])
- If anything is unclear or ambiguous, loop back to step 3 (AskUserQuestion)
- Present the plan to the user for approval before proceeding
### 5. Implement
- Add deps if missing: Bash `cargo add light-sdk@0.18 --features anchor,v2,cpi-context` and `cargo add light-sdk-macros@0.18 light-compressible@0.1 anchor-lang@0.31`
- Follow the guide and the approved plan
- Write/Edit to create or modify files
- TaskUpdate to mark each step done
### 6. Verify
- Bash `anchor build`
- Bash `anchor test` if tests exist
- TaskUpdate to mark complete
### Tools
- mcp__zkcompression__SearchLightProtocol("<query>") for API details
- mcp__deepwiki__ask_question("Lightprotocol/light-protocol", "<q>") for architecture
- Task subagent with Grep/Read/WebFetch for parallel lookups
- TaskList to check remaining work