Skip to content

feat(cct-sdk): Add createRecipientATA option to mint tokens op - #403

Merged
mervin-link merged 45 commits into
cct-sdkfrom
feat/DAPP-11328-create-recipient-ata
Sep 8, 2026
Merged

feat(cct-sdk): Add createRecipientATA option to mint tokens op#403
mervin-link merged 45 commits into
cct-sdkfrom
feat/DAPP-11328-create-recipient-ata

Conversation

@mervin-link

@mervin-link mervin-link commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

What

  • DAPP-11328
  • Add optional createRecipientATA to Solana mintTokens
  • Create the recipient ATA idempotently before minting when enabled
  • Update TSDoc and unit tests

Why

  • Allow minting to recipients without an existing ATA in a single transaction

@vercel

vercel Bot commented Sep 4, 2026

Copy link
Copy Markdown

You must have Developer access to commit code to Chainlink Labs on Vercel. If you contact an administrator and receive Developer access, commit again to see your changes.

Learn more: https://vercel.com/docs/accounts/team-members-and-roles/access-roles#team-level-roles

@aelmanaa

aelmanaa commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Overview

APPROVE — 0 blockers, 0 majors. Correct, idiomatic, and live-proven.


Grounding

vs. contract behavior:

  • When createRecipientATA: true, ATA creation instruction (idempotent via createAssociatedTokenAccountIdempotentInstruction) is prepended, then mint instruction executes in same tx
  • The ATA creation is skipped when it already exists — idempotent, no double-create error
  • When createRecipientATA: false (default), existing "ATA must exist" path unchanged
  • Validates the option is a boolean

Op composition pattern:

  • Uses new CreateTokenAccount().generate(chain, {...}) to build ATA creation instructions
  • Idiomatic: matches established pattern (DAPP-11329 includeApproval, merged applyChainUpdates)
  • Reuses sibling CreateTokenAccount op builder as single source of truth

Backwards compatible:

  • createRecipientATA defaults to false
  • Existing "ATA must exist" flow unchanged
  • No breaking changes to signatures

Implementation Details

Parse phase:

  • createRecipientATA defaults to false, validated as boolean, parsed into ParsedMintTokensParams

BuildUnsigned phase:

  • When createRecipientATA: true: calls new CreateTokenAccount().generate(); skips resolveExistingTokenAccount preflight (correct—ATA is being created)
  • When createRecipientATA: false: calls resolveExistingTokenAccount (existing path)
  • Instructions composed: [...createRecipientATA.instructions, mintToInstruction]
  • mainIndex set correctly: 1 when ATA creation included, 0 otherwise

Execute phase:

  • Wallet validation unchanged
  • Instruction composition handles everything

Test Coverage

11/11 passing ✅

  • baseline (1/1): Instruction structure unchanged
  • multisig (1/1): Token-2022 and SPL Token multisig support
  • create-ATA (1/1): 2 instructions when createRecipientATA: true, correct mainIndex, idempotent CreateIdempotent instruction
  • missing-ATA-error (1/1): Rejects missing ATA when option not set (pre-RPC)
  • max-amount (1/1): u64 encoding
  • authority-default (1/1): Payer fallback
  • validation (2/2): Invalid params, missing/non-token mints
  • execute (2/2): Multisig requirement, wallet validation, hash returned

Full coverage of both paths: createRecipientATA: true (create idempotently) and createRecipientATA: false (resolve existing).


Live Devnet Verification

Mint J9jfqxhsGM2U7V4aQ6Y2R37hDhVMMVCLYEoQMBzeJCbF, fresh recipient with no ATA:

Baseline (option off):

  • Correctly rejects: CCIPTokenAccountNotFoundError (the first-run dead-end this option removes)

createRecipientATA: true (fresh ATA):

Idempotency proof (ATA already exists):

Invalid type:

  • Bad createRecipientATA type → CCTParamsInvalidError "createRecipientATA: must be a boolean"

Recommendation

Ship it — closes the first-run mint dead-end cleanly, idiomatic composition, live-proven (atomic + idempotent). Backwards compatible, full test coverage. Ready to merge.


Optional: Robustness Note

The current code extracts tokenProgram from the instruction keys (createRecipientATA.instructions[0].keys.at(-1).pubkey) when the ATA is created. This works but is fragile—it relies on the internal key ordering of createAssociatedTokenAccountIdempotentInstruction. A more robust approach would be to call resolveATA(connection, tokenAddress, recipient) directly (same as CreateTokenAccount does internally) to get the tokenProgram cleanly. This isolates the operation from implementation details of the underlying SPL instruction.

For now, this is a polish point rather than a blocker, since the current approach works and tests pass.

Base automatically changed from feat/DAPP-11329-include-approval-option to feat/DAPP-11325-override-pending-admin September 7, 2026 09:05
Base automatically changed from feat/DAPP-11325-override-pending-admin to cct-sdk September 7, 2026 09:16
@mervin-link
mervin-link merged commit 6a4c222 into cct-sdk Sep 8, 2026
2 of 4 checks passed
@mervin-link
mervin-link deleted the feat/DAPP-11328-create-recipient-ata branch September 8, 2026 07:35
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.

3 participants