Skip to content

Commit 943a7ad

Browse files
author
tilo-14
committed
docs(sdk): document load-all behavior in approve/revoke JSDoc; add owner==feePayer E2E test
Add @remarks to approve/revoke functions documenting that for light-token mints, all cold (compressed) balances are loaded into the hot ATA regardless of the delegation amount. Add E2E test covering the owner==feePayer code path which was previously only tested at the unit level.
1 parent b5a21f3 commit 943a7ad

4 files changed

Lines changed: 105 additions & 22 deletions

File tree

js/compressed-token/src/v3/actions/approve-interface.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ import { sliceLast } from './slice-last';
2727
* Supports light-token, SPL, and Token-2022 mints. For light-token mints,
2828
* loads cold accounts if needed before sending the approve instruction.
2929
*
30+
* @remarks For light-token mints, all cold (compressed) balances are loaded
31+
* into the hot ATA, not just the delegation amount. The `amount` parameter
32+
* only controls the delegate's spending limit.
33+
*
3034
* @param rpc RPC connection
3135
* @param payer Fee payer (signer)
3236
* @param tokenAccount ATA address
@@ -111,6 +115,9 @@ export async function approveInterface(
111115
* Supports light-token, SPL, and Token-2022 mints. For light-token mints,
112116
* loads cold accounts if needed before sending the revoke instruction.
113117
*
118+
* @remarks For light-token mints, all cold (compressed) balances are loaded
119+
* into the hot ATA before the revoke instruction.
120+
*
114121
* @param rpc RPC connection
115122
* @param payer Fee payer (signer)
116123
* @param tokenAccount ATA address
@@ -177,13 +184,3 @@ export async function revokeInterface(
177184
const tx = buildAndSignTx(revokeIxs, payer, blockhash, additionalSigners);
178185
return sendAndConfirmTx(rpc, tx, confirmOptions);
179186
}
180-
181-
export {
182-
createApproveInterfaceInstructions,
183-
createRevokeInterfaceInstructions,
184-
} from '../instructions/approve-interface';
185-
export { sliceLast } from './slice-last';
186-
export {
187-
createLightTokenApproveInstruction,
188-
createLightTokenRevokeInstruction,
189-
} from '../instructions/approve-revoke';

js/compressed-token/src/v3/instructions/approve-interface.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,23 @@ function calculateApproveCU(loadBatch: InternalLoadBatch | null): number {
3737
return calculateCombinedCU(APPROVE_BASE_CU, loadBatch);
3838
}
3939

40+
const REVOKE_BASE_CU = 10_000;
41+
42+
function calculateRevokeCU(loadBatch: InternalLoadBatch | null): number {
43+
return calculateCombinedCU(REVOKE_BASE_CU, loadBatch);
44+
}
45+
4046
/**
4147
* Build instruction batches for approving a delegate on an ATA.
4248
*
4349
* Supports light-token, SPL, and Token-2022 mints.
4450
* Returns `TransactionInstruction[][]`. Send [0..n-2] in parallel, then [n-1].
4551
*
52+
* @remarks For light-token mints, all cold (compressed) balances are loaded
53+
* into the hot ATA before the approve instruction. The `amount` parameter
54+
* only controls the delegate's spending limit, not the number of accounts
55+
* loaded. Users with many cold accounts may see additional load transactions.
56+
*
4657
* @param rpc RPC connection
4758
* @param payer Fee payer public key
4859
* @param mint Mint address
@@ -185,6 +196,10 @@ export async function createApproveInterfaceInstructions(
185196
* Supports light-token, SPL, and Token-2022 mints.
186197
* Returns `TransactionInstruction[][]`. Send [0..n-2] in parallel, then [n-1].
187198
*
199+
* @remarks For light-token mints, all cold (compressed) balances are loaded
200+
* into the hot ATA before the revoke instruction. Users with many cold
201+
* accounts may see additional load transactions.
202+
*
188203
* @param rpc RPC connection
189204
* @param payer Fee payer public key
190205
* @param mint Mint address
@@ -234,7 +249,7 @@ export async function createRevokeInterfaceInstructions(
234249
const numSigners = payer.equals(owner) ? 1 : 2;
235250
const txIxs = [
236251
ComputeBudgetProgram.setComputeUnitLimit({
237-
units: APPROVE_BASE_CU,
252+
units: REVOKE_BASE_CU,
238253
}),
239254
revokeIx,
240255
];
@@ -264,7 +279,7 @@ export async function createRevokeInterfaceInstructions(
264279
const numSigners = payer.equals(owner) ? 1 : 2;
265280

266281
if (internalBatches.length === 0) {
267-
const cu = calculateApproveCU(null);
282+
const cu = calculateRevokeCU(null);
268283
const txIxs = [
269284
ComputeBudgetProgram.setComputeUnitLimit({ units: cu }),
270285
revokeIx,
@@ -275,7 +290,7 @@ export async function createRevokeInterfaceInstructions(
275290

276291
if (internalBatches.length === 1) {
277292
const batch = internalBatches[0];
278-
const cu = calculateApproveCU(batch);
293+
const cu = calculateRevokeCU(batch);
279294
const txIxs = [
280295
ComputeBudgetProgram.setComputeUnitLimit({ units: cu }),
281296
...batch.instructions,
@@ -299,7 +314,7 @@ export async function createRevokeInterfaceInstructions(
299314
}
300315

301316
const lastBatch = internalBatches[internalBatches.length - 1];
302-
const lastCu = calculateApproveCU(lastBatch);
317+
const lastCu = calculateRevokeCU(lastBatch);
303318
const lastTxIxs = [
304319
ComputeBudgetProgram.setComputeUnitLimit({ units: lastCu }),
305320
...lastBatch.instructions,

js/compressed-token/src/v3/unified/index.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,12 @@ import {
3939
import type { TransferOptions as _TransferOptions } from '../actions/transfer-interface';
4040
import {
4141
approveInterface as _approveInterface,
42-
createApproveInterfaceInstructions as _createApproveInterfaceInstructions,
4342
revokeInterface as _revokeInterface,
44-
createRevokeInterfaceInstructions as _createRevokeInterfaceInstructions,
4543
} from '../actions/approve-interface';
44+
import {
45+
createApproveInterfaceInstructions as _createApproveInterfaceInstructions,
46+
createRevokeInterfaceInstructions as _createRevokeInterfaceInstructions,
47+
} from '../instructions/approve-interface';
4648

4749
import { _getOrCreateAtaInterface } from '../actions/get-or-create-ata-interface';
4850
import {
@@ -506,6 +508,10 @@ export type {
506508
* Auto-detects mint type (light-token, SPL, or Token-2022) and dispatches
507509
* to the appropriate instruction.
508510
*
511+
* @remarks For light-token mints, all cold (compressed) balances are loaded
512+
* into the hot ATA, not just the delegation amount. The `amount` parameter
513+
* only controls the delegate's spending limit.
514+
*
509515
* @param rpc RPC connection
510516
* @param payer Fee payer (signer)
511517
* @param tokenAccount ATA address
@@ -526,7 +532,6 @@ export async function approveInterface(
526532
owner: Signer,
527533
confirmOptions?: ConfirmOptions,
528534
) {
529-
const mintInfo = await getMintInterface(rpc, mint);
530535
return _approveInterface(
531536
rpc,
532537
payer,
@@ -536,7 +541,7 @@ export async function approveInterface(
536541
amount,
537542
owner,
538543
confirmOptions,
539-
mintInfo.programId,
544+
undefined, // programId: use default LIGHT_TOKEN_PROGRAM_ID
540545
true, // wrap=true for unified
541546
);
542547
}
@@ -545,6 +550,11 @@ export async function approveInterface(
545550
* Build instruction batches for approving a delegate on an ATA.
546551
*
547552
* Auto-detects mint type (light-token, SPL, or Token-2022).
553+
*
554+
* @remarks For light-token mints, all cold (compressed) balances are loaded
555+
* into the hot ATA before the approve instruction. The `amount` parameter
556+
* only controls the delegate's spending limit, not the number of accounts
557+
* loaded.
548558
*/
549559
export async function createApproveInterfaceInstructions(
550560
rpc: Rpc,
@@ -567,7 +577,7 @@ export async function createApproveInterfaceInstructions(
567577
amount,
568578
owner,
569579
resolvedDecimals,
570-
mintInfo.programId,
580+
undefined, // programId: use default LIGHT_TOKEN_PROGRAM_ID
571581
true, // wrap=true for unified
572582
);
573583
}
@@ -578,6 +588,9 @@ export async function createApproveInterfaceInstructions(
578588
* Auto-detects mint type (light-token, SPL, or Token-2022) and dispatches
579589
* to the appropriate instruction.
580590
*
591+
* @remarks For light-token mints, all cold (compressed) balances are loaded
592+
* into the hot ATA before the revoke instruction.
593+
*
581594
* @param rpc RPC connection
582595
* @param payer Fee payer (signer)
583596
* @param tokenAccount ATA address
@@ -594,15 +607,14 @@ export async function revokeInterface(
594607
owner: Signer,
595608
confirmOptions?: ConfirmOptions,
596609
) {
597-
const mintInfo = await getMintInterface(rpc, mint);
598610
return _revokeInterface(
599611
rpc,
600612
payer,
601613
tokenAccount,
602614
mint,
603615
owner,
604616
confirmOptions,
605-
mintInfo.programId,
617+
undefined, // programId: use default LIGHT_TOKEN_PROGRAM_ID
606618
true, // wrap=true for unified
607619
);
608620
}
@@ -611,6 +623,9 @@ export async function revokeInterface(
611623
* Build instruction batches for revoking delegation on an ATA.
612624
*
613625
* Auto-detects mint type (light-token, SPL, or Token-2022).
626+
*
627+
* @remarks For light-token mints, all cold (compressed) balances are loaded
628+
* into the hot ATA before the revoke instruction.
614629
*/
615630
export async function createRevokeInterfaceInstructions(
616631
rpc: Rpc,
@@ -629,7 +644,7 @@ export async function createRevokeInterfaceInstructions(
629644
tokenAccount,
630645
owner,
631646
resolvedDecimals,
632-
mintInfo.programId,
647+
undefined, // programId: use default LIGHT_TOKEN_PROGRAM_ID
633648
true, // wrap=true for unified
634649
);
635650
}

js/compressed-token/tests/e2e/approve-revoke-light-token.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,62 @@ describe('LightToken approve/revoke - E2E', () => {
396396
expect(info.delegatedAmount).toBe(BigInt(700));
397397
}, 60_000);
398398

399+
it('should approve and revoke when owner is also fee payer', async () => {
400+
const owner = await newAccountWithLamports(rpc, 1e9);
401+
const delegate = Keypair.generate();
402+
const lightTokenAta = getAssociatedTokenAddressInterface(
403+
mint,
404+
owner.publicKey,
405+
);
406+
407+
// Create hot ATA and mint tokens (use global payer for setup)
408+
await createAtaInterfaceIdempotent(rpc, payer, mint, owner.publicKey);
409+
await mintTo(
410+
rpc,
411+
payer,
412+
mint,
413+
owner.publicKey,
414+
mintAuthority,
415+
bn(1000),
416+
stateTreeInfo,
417+
selectTokenPoolInfo(tokenPoolInfos),
418+
);
419+
await loadAta(rpc, lightTokenAta, owner, mint, payer);
420+
421+
// Approve with owner as fee payer (omit feePayer param)
422+
const approveIx = createLightTokenApproveInstruction(
423+
lightTokenAta,
424+
delegate.publicKey,
425+
owner.publicKey,
426+
BigInt(500),
427+
);
428+
let { blockhash } = await rpc.getLatestBlockhash();
429+
// owner is sole signer — acts as both tx fee payer and instruction owner
430+
let tx = buildAndSignTx([approveIx], owner, blockhash);
431+
await sendAndConfirmTx(rpc, tx);
432+
433+
// Verify delegate is set
434+
const { delegate: actualDelegate, delegatedAmount } =
435+
await getLightTokenDelegate(rpc, lightTokenAta);
436+
expect(actualDelegate).not.toBeNull();
437+
expect(actualDelegate!.equals(delegate.publicKey)).toBe(true);
438+
expect(delegatedAmount).toBe(BigInt(500));
439+
440+
// Revoke with owner as fee payer
441+
const revokeIx = createLightTokenRevokeInstruction(
442+
lightTokenAta,
443+
owner.publicKey,
444+
);
445+
({ blockhash } = await rpc.getLatestBlockhash());
446+
tx = buildAndSignTx([revokeIx], owner, blockhash);
447+
await sendAndConfirmTx(rpc, tx);
448+
449+
// Verify delegate is cleared
450+
const afterRevoke = await getLightTokenDelegate(rpc, lightTokenAta);
451+
expect(afterRevoke.delegate).toBeNull();
452+
expect(afterRevoke.delegatedAmount).toBe(BigInt(0));
453+
}, 60_000);
454+
399455
it('should fail when non-owner tries to approve', async () => {
400456
const owner = await newAccountWithLamports(rpc, 1e9);
401457
const wrongSigner = Keypair.generate();

0 commit comments

Comments
 (0)