Skip to content

feat(sdk): add approve/revoke delegation for light-token ATAs#2351

Open
tilo-14 wants to merge 5 commits intomainfrom
feat/approve-revoke-light-token
Open

feat(sdk): add approve/revoke delegation for light-token ATAs#2351
tilo-14 wants to merge 5 commits intomainfrom
feat/approve-revoke-light-token

Conversation

@tilo-14
Copy link
Member

@tilo-14 tilo-14 commented Mar 18, 2026

Summary

  • Add createLightTokenApproveInstruction (discriminator 4) and createLightTokenRevokeInstruction (discriminator 5) instruction builders matching Rust SDK account layout
  • Add approveInterface / revokeInterface async actions with cold loading + tx sending, following the transferInterface pattern
  • Add getLightTokenDelegate test helper and extend FrozenOperation type with 'approve' | 'revoke'
  • Export all new functions from src/index.ts and src/v3/unified/index.ts

Test plan

  • Unit tests: discriminator encoding, account meta order/flags, amount u64 LE encoding, separate fee payer handling (9 tests)
  • E2E tests: approve delegate, revoke delegate, overwrite delegate, non-owner rejection, separate fee payer (5 tests)
  • LIGHT_PROTOCOL_VERSION=V2 pnpm run build passes (all 5 bundles)
  • All 14 tests pass via LIGHT_PROTOCOL_VERSION=V2 npx vitest run tests/e2e/approve-revoke-light-token.test.ts

Open with Devin

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 18, 2026

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (14)
  • js/compressed-token/src/index.ts is excluded by none and included by none
  • js/compressed-token/src/v3/actions/approve-interface.ts is excluded by none and included by none
  • js/compressed-token/src/v3/actions/index.ts is excluded by none and included by none
  • js/compressed-token/src/v3/actions/transfer-delegated-interface.ts is excluded by none and included by none
  • js/compressed-token/src/v3/get-account-interface.ts is excluded by none and included by none
  • js/compressed-token/src/v3/instructions/approve-revoke.ts is excluded by none and included by none
  • js/compressed-token/src/v3/instructions/index.ts is excluded by none and included by none
  • js/compressed-token/src/v3/unified/index.ts is excluded by none and included by none
  • js/compressed-token/tests/e2e/approve-revoke-light-token.test.ts is excluded by none and included by none
  • js/compressed-token/tests/e2e/approve-revoke-spl-t22.test.ts is excluded by none and included by none
  • js/compressed-token/tests/e2e/light-token-account-helpers.ts is excluded by none and included by none
  • js/compressed-token/tests/e2e/transfer-delegated-failures.test.ts is excluded by none and included by none
  • js/compressed-token/tests/e2e/transfer-delegated-interface.test.ts is excluded by none and included by none
  • js/compressed-token/tests/e2e/transfer-delegated-spl-t22.test.ts is excluded by none and included by none

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: fc87c785-b7c4-4032-8dfd-96d69108fb35

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/approve-revoke-light-token
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@tilo-14 tilo-14 changed the title feat(compressed-token): add approve/revoke delegation for light-token ATAs feat(sdk): add approve/revoke delegation for light-token ATAs Mar 18, 2026
devin-ai-integration[bot]

This comment was marked as resolved.

@tilo-14 tilo-14 force-pushed the feat/approve-revoke-light-token branch 2 times, most recently from 84166d7 to 13ab9fe Compare March 19, 2026 21:32
Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 11 additional findings in Devin Review.

Open in Devin Review

Comment on lines +655 to +678
export async function transferDelegatedInterface(
rpc: Rpc,
payer: Signer,
source: PublicKey,
mint: PublicKey,
recipient: PublicKey,
delegate: Signer,
owner: PublicKey,
amount: number | bigint | BN,
confirmOptions?: ConfirmOptions,
) {
const mintInfo = await getMintInterface(rpc, mint);
return _transferDelegatedInterface(
rpc,
payer,
source,
mint,
recipient,
delegate,
owner,
amount,
confirmOptions,
mintInfo.programId,
);
Copy link

@devin-ai-integration devin-ai-integration bot Mar 19, 2026

Choose a reason for hiding this comment

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

🔴 Unified path transferDelegatedInterface and createTransferDelegatedInterfaceInstructions don't force wrap = true

Every other unified transfer function explicitly forces wrap = true to auto-wrap SPL/T22 balances into the canonical light-token ATA. However, transferDelegatedInterface (unified/index.ts:655-678) and createTransferDelegatedInterfaceInstructions (unified/index.ts:686-708) delegate directly to their standard-path counterparts without injecting wrap: true. The standard _transferDelegatedInterface (transfer-delegated-interface.ts:53) calls transferInterface without passing wrap, so it defaults to false. Similarly, _createTransferDelegatedInterfaceInstructions (transfer-delegated-interface.ts:97) passes { owner, programId } without wrap.

This means delegated transfers via the /unified import won't wrap SPL/T22 balances before transferring, breaking the unified path's core contract. If a delegate is approved for an amount that requires wrapping SPL/T22 tokens, the transfer fails with "Insufficient balance" instead of wrapping and succeeding.

Comparison with correctly-wrapped unified functions

Unified transferInterface (line 249-262) correctly passes wrap: true:

return _transferInterface(
    rpc, payer, source, mint, recipient, owner, amount,
    undefined,
    confirmOptions, options,
    true, // wrap=true for unified
    decimals,
);

But unified transferDelegatedInterface (line 667) does NOT:

return _transferDelegatedInterface(
    rpc, payer, source, mint, recipient, delegate, owner, amount,
    confirmOptions, mintInfo.programId,
);
Open in Devin Review

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

tilo-14 added 5 commits March 20, 2026 00:34
… ATAs

Add TypeScript SDK functions to call the on-chain CTokenApprove (discriminator 4)
and CTokenRevoke (discriminator 5) instruction handlers for light-token associated
token accounts.

New files:
- instructions/approve-revoke.ts: sync instruction builders matching Rust SDK layout
- actions/approve-interface.ts: async actions with cold loading + tx sending
- tests/e2e/approve-revoke-light-token.test.ts: unit + E2E tests

Also adds getLightTokenDelegate helper and extends FrozenOperation type.
Avoid unnecessary getMintInterface RPC call when caller provides decimals.
Add transferDelegatedInterface action and unified wrapper, completing
the approve → transfer → revoke delegation flow for light-token ATAs.
Update transferDelegatedInterface and
createTransferDelegatedInterfaceInstructions to accept a recipient
wallet address instead of an explicit destination token account,
matching the transferInterface convention from PR #2354.

ATA derivation and idempotent creation now happen internally for
all programId variants (light-token, SPL, Token-2022).
@tilo-14 tilo-14 force-pushed the feat/approve-revoke-light-token branch from 13ab9fe to 81d9662 Compare March 20, 2026 00:35
* @param programId Token program ID (default: LIGHT_TOKEN_PROGRAM_ID)
* @returns Transaction signature
*/
export async function transferDelegatedInterface(
Copy link
Contributor

@SwenSchaeferjohann SwenSchaeferjohann Mar 20, 2026

Choose a reason for hiding this comment

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

remove wrapper - the tests and clients just call transferInterface / createTransferInterfaceInstructions / createTransferToAccountInterfaceInstructions - passing the delegate as the owner/signer.

and those functions should handle it just fine @tilo-14

* @param programId Token program ID (default: LIGHT_TOKEN_PROGRAM_ID)
* @returns Instruction batches
*/
export async function createRevokeInterfaceInstructions(
Copy link
Contributor

Choose a reason for hiding this comment

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

createXInterfaceInstructions should be extracted into their own files in the instructions dir

@tilo-14

decimals,
);

const revokeIx = createLightTokenRevokeInstruction(
Copy link
Contributor

Choose a reason for hiding this comment

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

seems we currently don't use xInterface as a true superset of spl.

IE
if the source is a hot spl account, approve and revoke should internally dispatch to provided token program's native instruction if wrap=false. the token-programId is passed by client, and defaults to light-token

if wrap=true, (this is if importing via /unified, or if manually passed by client via an optional param, which also seems to be missing), they should internally dispatch to 1) wrap spl to light 2) approve/revoke on the light token account

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants