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
2 changes: 1 addition & 1 deletion cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lightprotocol/zk-compression-cli",
"version": "0.28.0-beta.10",
"version": "0.28.0-beta.12",
"description": "ZK Compression: Secure Scaling on Solana",
"maintainers": [
{
Expand Down
19 changes: 19 additions & 0 deletions js/compressed-token/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
## [0.23.0-beta.11]

### Added

- **Delegate approval and revocation** for SPL Token, Token-2022, and light-token, aligned with existing interface helpers:
- **Actions:** `approveInterface`, `revokeInterface`.
- **Instruction builders:** `createApproveInterfaceInstructions`, `createRevokeInterfaceInstructions` — each inner array is one transaction’s instructions (same batching style as other interface instruction builders).
- **Program-level helpers:** `createLightTokenApproveInstruction`, `createLightTokenRevokeInstruction`
- **Shared options:** approve/revoke accept optional `InterfaceOptions` (same type as `transferInterface`), including `splInterfaceInfos` when you need to supply SPL interface pool accounts explicitly.

### Changed

- **`approveInterface` / `revokeInterface`:** optional `options?: InterfaceOptions` and `decimals?: number` after `wrap`. For SPL or Token-2022 with `wrap: false`, the SDK skips an extra mint fetch used only for decimals on that path (you can still pass `decimals` when your flow requires it).
- **`@lightprotocol/compressed-token/unified`:** approve/revoke APIs accept the same optional `options` and `decimals`; unified entrypoints keep their existing default wrapping behavior (`wrap: true`).

### Fixed

- **Browser bundles:** Terser no longer rewrites booleans to integers in minified output, keeping `AccountMeta` flags compatible with `@solana/web3.js` and runtime expectations (same change as `stateless.js`; see [#2347](https://github.com/Lightprotocol/light-protocol/pull/2347)).

## [0.23.0-beta.10]

### Breaking Changes
Expand Down
2 changes: 1 addition & 1 deletion js/compressed-token/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lightprotocol/compressed-token",
"version": "0.23.0-beta.10",
"version": "0.23.0-beta.12",
"description": "JS client to interact with the compressed-token program",
"sideEffects": false,
"main": "dist/cjs/node/index.cjs",
Expand Down
2 changes: 1 addition & 1 deletion js/compressed-token/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const rolls = (fmt, env) => ({
drop_console: false,
drop_debugger: true,
passes: 3,
booleans_as_integers: true,
booleans_as_integers: false,
keep_fargs: false,
keep_fnames: false,
keep_infinity: true,
Expand Down
6 changes: 6 additions & 0 deletions js/compressed-token/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ export {
createLightTokenThawAccountInstruction,
createLightTokenTransferInstruction,
createLightTokenTransferCheckedInstruction,
createLightTokenApproveInstruction,
createLightTokenRevokeInstruction,
// Types
TokenMetadataInstructionData,
CompressibleConfig,
Expand All @@ -80,6 +82,10 @@ export {
createTransferInterfaceInstructions,
createTransferToAccountInterfaceInstructions,
sliceLast,
approveInterface,
createApproveInterfaceInstructions,
revokeInterface,
createRevokeInterfaceInstructions,
wrap,
mintTo as mintToLightToken,
mintToCompressed,
Expand Down
193 changes: 193 additions & 0 deletions js/compressed-token/src/v3/actions/approve-interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import {
ConfirmOptions,
PublicKey,
Signer,
TransactionSignature,
} from '@solana/web3.js';
import {
Rpc,
buildAndSignTx,
sendAndConfirmTx,
dedupeSigner,
assertBetaEnabled,
LIGHT_TOKEN_PROGRAM_ID,
} from '@lightprotocol/stateless.js';
import BN from 'bn.js';
import {
createApproveInterfaceInstructions,
createRevokeInterfaceInstructions,
} from '../instructions/approve-interface';
import { getAssociatedTokenAddressInterface } from '../get-associated-token-address-interface';
import { getMintInterface } from '../get-mint-interface';
import { sliceLast } from './slice-last';
import type { InterfaceOptions } from './transfer-interface';
import { TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID } from '@solana/spl-token';

/**
* Approve a delegate for an associated token account.
*
* Supports light-token, SPL, and Token-2022 mints. For light-token mints,
* loads cold accounts if needed before sending the approve instruction.
*
* @remarks For light-token mints, all cold (compressed) balances are loaded
* into the hot ATA, not just the delegation amount. The `amount` parameter
* only controls the delegate's spending limit.
*
* @param rpc RPC connection
* @param payer Fee payer (signer)
* @param tokenAccount ATA address
* @param mint Mint address
* @param delegate Delegate to approve
* @param amount Amount to delegate
* @param owner Owner of the token account (signer)
* @param confirmOptions Optional confirm options
* @param programId Token program ID (default: LIGHT_TOKEN_PROGRAM_ID)
* @param wrap When true and mint is SPL/T22, wrap into light-token then approve
* @returns Transaction signature
*/
export async function approveInterface(
rpc: Rpc,
payer: Signer,
tokenAccount: PublicKey,
mint: PublicKey,
delegate: PublicKey,
amount: number | bigint | BN,
owner: Signer,
confirmOptions?: ConfirmOptions,
programId: PublicKey = LIGHT_TOKEN_PROGRAM_ID,
wrap = false,
options?: InterfaceOptions,
decimals?: number,
): Promise<TransactionSignature> {
assertBetaEnabled();

const expectedAta = getAssociatedTokenAddressInterface(
mint,
owner.publicKey,
false,
programId,
);
if (!tokenAccount.equals(expectedAta)) {
throw new Error(
`Token account mismatch. Expected ${expectedAta.toBase58()}, got ${tokenAccount.toBase58()}`,
);
}

const isSplOrT22 =
programId.equals(TOKEN_PROGRAM_ID) ||
programId.equals(TOKEN_2022_PROGRAM_ID);
const resolvedDecimals =
decimals ??
(isSplOrT22 && !wrap
? 0
: (await getMintInterface(rpc, mint)).mint.decimals);
const batches = await createApproveInterfaceInstructions(
rpc,
payer.publicKey,
mint,
tokenAccount,
delegate,
amount,
owner.publicKey,
resolvedDecimals,
programId,
wrap,
options,
);

const additionalSigners = dedupeSigner(payer, [owner]);
const { rest: loads, last: approveIxs } = sliceLast(batches);

await Promise.all(
loads.map(async ixs => {
const { blockhash } = await rpc.getLatestBlockhash();
const tx = buildAndSignTx(ixs, payer, blockhash, additionalSigners);
return sendAndConfirmTx(rpc, tx, confirmOptions);
}),
);

const { blockhash } = await rpc.getLatestBlockhash();
const tx = buildAndSignTx(approveIxs, payer, blockhash, additionalSigners);
return sendAndConfirmTx(rpc, tx, confirmOptions);
}

/**
* Revoke delegation for an associated token account.
*
* Supports light-token, SPL, and Token-2022 mints. For light-token mints,
* loads cold accounts if needed before sending the revoke instruction.
*
* @remarks For light-token mints, all cold (compressed) balances are loaded
* into the hot ATA before the revoke instruction.
*
* @param rpc RPC connection
* @param payer Fee payer (signer)
* @param tokenAccount ATA address
* @param mint Mint address
* @param owner Owner of the token account (signer)
* @param confirmOptions Optional confirm options
* @param programId Token program ID (default: LIGHT_TOKEN_PROGRAM_ID)
* @param wrap When true and mint is SPL/T22, wrap into light-token then revoke
* @returns Transaction signature
*/
export async function revokeInterface(
rpc: Rpc,
payer: Signer,
tokenAccount: PublicKey,
mint: PublicKey,
owner: Signer,
confirmOptions?: ConfirmOptions,
programId: PublicKey = LIGHT_TOKEN_PROGRAM_ID,
wrap = false,
options?: InterfaceOptions,
decimals?: number,
): Promise<TransactionSignature> {
assertBetaEnabled();

const expectedAta = getAssociatedTokenAddressInterface(
mint,
owner.publicKey,
false,
programId,
);
if (!tokenAccount.equals(expectedAta)) {
throw new Error(
`Token account mismatch. Expected ${expectedAta.toBase58()}, got ${tokenAccount.toBase58()}`,
);
}

const isSplOrT22 =
programId.equals(TOKEN_PROGRAM_ID) ||
programId.equals(TOKEN_2022_PROGRAM_ID);
const resolvedDecimals =
decimals ??
(isSplOrT22 && !wrap
? 0
: (await getMintInterface(rpc, mint)).mint.decimals);
const batches = await createRevokeInterfaceInstructions(
rpc,
payer.publicKey,
mint,
tokenAccount,
owner.publicKey,
resolvedDecimals,
programId,
wrap,
options,
);

const additionalSigners = dedupeSigner(payer, [owner]);
const { rest: loads, last: revokeIxs } = sliceLast(batches);

await Promise.all(
loads.map(async ixs => {
const { blockhash } = await rpc.getLatestBlockhash();
const tx = buildAndSignTx(ixs, payer, blockhash, additionalSigners);
return sendAndConfirmTx(rpc, tx, confirmOptions);
}),
);

const { blockhash } = await rpc.getLatestBlockhash();
const tx = buildAndSignTx(revokeIxs, payer, blockhash, additionalSigners);
return sendAndConfirmTx(rpc, tx, confirmOptions);
}
1 change: 1 addition & 0 deletions js/compressed-token/src/v3/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export * from './transfer-interface';
export * from './wrap';
export * from './unwrap';
export * from './load-ata';
export * from './approve-interface';
7 changes: 6 additions & 1 deletion js/compressed-token/src/v3/get-account-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,12 @@ function throwIfUnexpectedRpcErrors(
}
}

export type FrozenOperation = 'load' | 'transfer' | 'unwrap';
export type FrozenOperation =
| 'load'
| 'transfer'
| 'unwrap'
| 'approve'
| 'revoke';

export function checkNotFrozen(
iface: AccountInterface,
Expand Down
Loading
Loading