pinocchio_tkn/
lib.rs

1//! # pinocchio-tkn
2//!
3//! **The complete Token toolkit for Pinocchio programs on Solana.**
4//!
5//! `pinocchio-tkn` provides zero-copy, zero-allocation CPI helpers for both SPL Token (legacy)
6//! and Token-2022 programs, with full support for Token-2022 extensions.
7//!
8//! ## Features
9//!
10//! - **Complete Coverage**: 60 instructions across SPL Token and Token-2022
11//!   - 19 common instructions (work with both programs)
12//!   - 41 Token-2022 exclusive extension instructions
13//! - **All 22 Token-2022 Extensions**: Full implementation of 19 extensions, documented stubs for 3 confidential extensions
14//! - **Unified API**: Single interface for both SPL Token and Token-2022
15//! - **Zero Allocations**: Stack-only operations, perfect for Solana's compute budget
16//! - **Type-Safe**: Builder-style APIs with compile-time guarantees
17//! - **State Parsing**: Zero-copy deserialization of Mint and TokenAccount data
18//! - **Comprehensive Helpers**: Calculations, validations, and detection utilities
19//! - **Production Ready**: Extensively tested with 100+ integration and E2E tests
20//!
21//! ## Why pinocchio-tkn?
22//!
23//! While official packages are fragmented across multiple crates:
24//! - `pinocchio-token`: Legacy SPL Token only
25//! - `pinocchio-token-2022`: Core instructions only, no extensions
26//! - No unified interface between the two
27//!
28//! `pinocchio-tkn` unifies everything into a single, complete package with a consistent API.
29//!
30//! ## Quick Start
31//!
32//! Add to your `Cargo.toml`:
33//!
34//! ```toml
35//! [dependencies]
36//! pinocchio-tkn = "0.2"
37//! ```
38//!
39//! ### Basic Transfer
40//!
41//! ```rust,ignore
42//! # fn main() -> pinocchio::ProgramResult {
43//! # use pinocchio::account_info::AccountInfo;
44//! # use pinocchio_tkn::prelude::*;
45//! # let source: &AccountInfo = todo!();
46//! # let destination: &AccountInfo = todo!();
47//! # let authority: &AccountInfo = todo!();
48//! // Works with both SPL Token and Token-2022
49//! // Defaults to Token-2022 if program_id is None
50//! Transfer {
51//!     source,
52//!     destination,
53//!     authority,
54//!     amount: 1_000_000,
55//!     program_id: None,
56//! }.invoke()?;
57//! # Ok(())
58//! # }
59//! ```
60//!
61//! ### Using Token-2022 Extensions
62//!
63//! ```rust,ignore
64//! use pinocchio::account_info::AccountInfo;
65//! use pinocchio_tkn::prelude::*;
66//! use pinocchio_tkn::extensions::*;
67//!
68//! // Initialize a mint with transfer fees
69//! InitializeTransferFeeConfig {
70//!     mint,
71//!     transfer_fee_config_authority: Some(authority_key),
72//!     withdraw_withheld_authority: Some(authority_key),
73//!     transfer_fee_basis_points: 100,  // 1%
74//!     maximum_fee: 5000,
75//! }.invoke()?;
76//!
77//! // Transfer with fees
78//! TransferCheckedWithFee {
79//!     source,
80//!     mint,
81//!     destination,
82//!     authority,
83//!     amount: 1_000_000,
84//!     decimals: 9,
85//!     fee: 10_000,
86//! }.invoke()?;
87//! ```
88//!
89//! ### State Parsing
90//!
91//! ```rust,ignore
92//! # fn main() -> pinocchio::ProgramResult {
93//! # use pinocchio::account_info::AccountInfo;
94//! # use pinocchio::program_error::ProgramError;
95//! # use pinocchio_tkn::state::{Mint, TokenAccount};
96//! # let mint_account: &AccountInfo = todo!();
97//! # let account: &AccountInfo = todo!();
98//! # let required_amount: u64 = 1;
99//! // Zero-copy parsing of account data
100//! let mint = Mint::from_account_info(&mint_account)?;
101//! let supply = mint.supply();
102//! let decimals = mint.decimals();
103//!
104//! let token_account = TokenAccount::from_account_info(&account)?;
105//! if token_account.amount() < required_amount {
106//!     return Err(ProgramError::InsufficientFunds);
107//! }
108//! # Ok(())
109//! # }
110//! ```
111//!
112//! ## Module Organization
113//!
114//! The crate is organized into feature-gated modules:
115//!
116//! - [`common`]: Instructions that work with both SPL Token and Token-2022 (19 instructions)
117//! - [`extensions`]: Token-2022 exclusive extensions (41 instructions across 22 extensions)
118//! - [`state`]: Zero-copy state parsing for Mint and TokenAccount
119//! - [`helpers`]: Utility functions for calculations, validation, and detection
120//! - [`prelude`]: Convenient re-exports of commonly used items
121//!
122//! ## Architecture Overview
123//!
124//! ```text
125//! ┌─────────────────────────────────────────────────────────────┐
126//! │                      pinocchio-tkn                          │
127//! ├─────────────────────────────────────────────────────────────┤
128//! │                                                             │
129//! │  ┌───────────┐  ┌──────────────┐  ┌───────┐  ┌─────────┐ │
130//! │  │  common   │  │  extensions  │  │ state │  │ helpers │ │
131//! │  ├───────────┤  ├──────────────┤  ├───────┤  ├─────────┤ │
132//! │  │Transfer   │  │TransferFee   │  │ Mint  │  │  calc   │ │
133//! │  │MintTo     │  │Metadata      │  │Token  │  │  valid  │ │
134//! │  │Burn       │  │Interest      │  │Account│  │ detect  │ │
135//! │  │Approve    │  │Pointers      │  └───────┘  └─────────┘ │
136//! │  │...        │  │Misc (13+)    │                          │
137//! │  └───────────┘  └──────────────┘                          │
138//! │                                                             │
139//! ├─────────────────────────────────────────────────────────────┤
140//! │              Pinocchio (zero-alloc runtime)                │
141//! ├─────────────────────────────────────────────────────────────┤
142//! │         SPL Token          │      Token-2022               │
143//! │   (TokenkegQfe...VQ5DA)   │  (TokenzQdBNb...xuEb)        │
144//! └─────────────────────────────────────────────────────────────┘
145//! ```
146//!
147//! ## Feature Flags
148//!
149//! The crate uses granular feature flags for optimal compile times and binary size:
150//!
151//! ```toml
152//! # Default features (most commonly used)
153//! default = ["common", "state", "helpers"]
154//!
155//! # Core features
156//! common = ["state"]        # Common instructions (both programs)
157//! state = []                 # State parsing (Mint, TokenAccount)
158//! helpers = []               # Calculation & validation utilities
159//!
160//! # Extension groups (Token-2022 only)
161//! ext-transfer-fee = []      # Transfer fee instructions (6)
162//! ext-metadata = []          # On-chain metadata (5)
163//! ext-interest = []          # Interest-bearing tokens (2)
164//! ext-pointers = []          # Metadata/Group/Hook pointers (8)
165//! ext-misc = []              # CPI Guard, Memo, Pause, etc (13)
166//!
167//! # Convenience features
168//! extensions = [             # All Token-2022 extensions
169//!     "ext-transfer-fee",
170//!     "ext-metadata",
171//!     "ext-interest",
172//!     "ext-pointers",
173//!     "ext-misc"
174//! ]
175//! full = [                   # Everything
176//!     "common",
177//!     "state",
178//!     "helpers",
179//!     "extensions"
180//! ]
181//! ```
182//!
183//! ## Security Considerations
184//!
185//! When building Solana programs with token operations, always:
186//!
187//! 1. **Validate account ownership**: Use [`helpers::assert_owned_by`] to ensure accounts
188//!    are owned by the correct token program
189//! 2. **Check account state**: Use [`helpers::assert_is_mint`] and [`helpers::assert_is_token_account`]
190//!    to validate account structure
191//! 3. **Verify authorities**: Use [`helpers::assert_mint_authority`] and similar validators
192//! 4. **Handle frozen accounts**: Check [`helpers::assert_account_not_frozen`] before transfers
193//! 5. **Validate amounts**: Use `TransferChecked` instead of `Transfer` to verify decimals
194//!
195//! ## Token-2022 Extension Coverage
196//!
197//! ### Fully Implemented (19 extensions, 42 instructions)
198//!
199//! - **Transfer Fee** (6 instructions): Assess fees on transfers
200//! - **Metadata Pointer** (2): Point to on-chain or off-chain metadata
201//! - **Token Metadata** (5): Store name, symbol, URI on-chain
202//! - **Transfer Hook** (2): Custom program hooks on transfers
203//! - **Group Pointer** (2): Token collection/grouping
204//! - **Group Member Pointer** (2): Collection membership
205//! - **Default Account State** (2): Auto-freeze new accounts
206//! - **Permanent Delegate** (1): Immutable delegate authority
207//! - **Non-Transferable** (1): Soul-bound tokens
208//! - **Interest Bearing** (2): Accrue interest over time
209//! - **CPI Guard** (2): Prevent CPI access to accounts
210//! - **Memo Transfer** (2): Require memos on transfers
211//! - **Pausable** (3): Pause/resume all token operations
212//! - **Scaled UI Amount** (2): Non-standard decimal display
213//! - **Mint Close Authority** (1): Allow mints to be closed
214//! - **Immutable Owner** (1): Prevent ownership changes
215//! - **Reallocate** (1): Resize accounts for new extensions
216//! - **Withdraw Excess Lamports** (1): Clean up account rent
217//!
218//! ### Documented (3 extensions - ZK proof required)
219//!
220//! These extensions require zero-knowledge cryptography and are provided as
221//! documented stubs with full explanations:
222//!
223//! - **Confidential Transfer**: Private balance transfers using ElGamal encryption
224//! - **Confidential Transfer Fee**: Fee collection with confidential amounts
225//! - **Confidential Mint/Burn**: Private minting and burning operations
226//!
227//! ## Performance
228//!
229//! All operations are designed for Solana's constrained environment:
230//!
231//! - **Zero heap allocations**: All data structures are stack-allocated
232//! - **Minimal compute units**: Optimized instruction building
233//! - **Compile-time optimizations**: Extensive use of `#[inline]` and const functions
234//! - **Small binary size**: Granular feature flags reduce code size
235//!
236//! ## Usage Guides
237//!
238//! ### A. Basic Token Creation (SPL Token & Token-2022)
239//!
240//! Creating a simple token without extensions works with both programs:
241//!
242//! ```rust,ignore
243//! # fn main() -> pinocchio::ProgramResult {
244//! # use pinocchio::account_info::AccountInfo;
245//! # use pinocchio::pubkey::Pubkey;
246//! use pinocchio_tkn::prelude::*;
247//! use pinocchio::system::CreateAccount;
248//! # let payer: &AccountInfo = todo!();
249//! # let mint: &AccountInfo = todo!();
250//! # let authority: &AccountInfo = todo!();
251//! # let mint_authority: &Pubkey = todo!();
252//! # let freeze_authority: &Pubkey = todo!();
253//! # let user_token_account: &AccountInfo = todo!();
254//! # let user: &AccountInfo = todo!();
255//! # let user_owner: &Pubkey = todo!();
256//! # let recipient_token_account: &AccountInfo = todo!();
257//!
258//! // Step 1: Create and allocate the mint account
259//! // For SPL Token or Token-2022 without extensions, mint size is 82 bytes
260//! let mint_space = 82;
261//! let rent_lamports = 0; /* calculate rent for 82 bytes */
262//!
263//! // Create the mint account (using system program)
264//! CreateAccount {
265//!     from: payer,
266//!     to: mint,
267//!     lamports: rent_lamports,
268//!     space: mint_space,
269//!     owner: &TOKEN_2022_PROGRAM_ID, // or &TOKEN_PROGRAM_ID for legacy
270//! }.invoke()?;
271//!
272//! // Step 2: Initialize the mint
273//! InitializeMint2 {
274//!     mint,
275//!     mint_authority,
276//!     freeze_authority: Some(freeze_authority),
277//!     decimals: 9, // 9 decimals like SOL
278//! }.invoke()?;
279//!
280//! // Step 3: Create a token account for a user
281//! let account_space = 165;
282//! let account_rent = 0; /* calculate rent for 165 bytes */
283//!
284//! CreateAccount {
285//!     from: payer,
286//!     to: user_token_account,
287//!     lamports: account_rent,
288//!     space: account_space,
289//!     owner: &TOKEN_2022_PROGRAM_ID,
290//! }.invoke()?;
291//!
292//! InitializeAccount3 {
293//!     account: user_token_account,
294//!     mint,
295//!     owner: user_owner,
296//! }.invoke()?;
297//!
298//! // Step 4: Mint tokens to the user
299//! MintToChecked {
300//!     mint,
301//!     account: user_token_account,
302//!     mint_authority: authority,
303//!     amount: 1_000_000_000, // 1 token with 9 decimals
304//!     decimals: 9,
305//! }.invoke()?;
306//!
307//! // Step 5: Transfer tokens between accounts
308//! TransferChecked {
309//!     source: user_token_account,
310//!     mint,
311//!     destination: recipient_token_account,
312//!     authority: user,
313//!     amount: 100_000_000, // 0.1 token
314//!     decimals: 9,
315//! }.invoke()?;
316//! # Ok(())
317//! # }
318//! ```
319//!
320//! **Key Points:**
321//! - Use `InitializeMint2` (preferred) instead of `InitializeMint` for cleaner API
322//! - Use `InitializeAccount3` (preferred) for token accounts
323//! - Always use "Checked" variants (`MintToChecked`, `TransferChecked`) for safety
324//! - Program ID (`TOKEN_PROGRAM_ID` vs `TOKEN_2022_PROGRAM_ID`) must match account owner
325//!
326//! ### B. Token-2022 with Single Extension
327//!
328//! Adding transfer fees to a token:
329//!
330//! ```rust,ignore
331//! # fn main() -> pinocchio::ProgramResult {
332//! # use pinocchio::account_info::AccountInfo;
333//! # use pinocchio::pubkey::Pubkey;
334//! use pinocchio_tkn::prelude::*;
335//! use pinocchio_tkn::helpers::{mint_space_for_extensions, ExtensionType, account_space_for_extensions};
336//! use pinocchio::system::CreateAccount;
337//! # let payer: &AccountInfo = todo!();
338//! # let mint: &AccountInfo = todo!();
339//! # let authority: &AccountInfo = todo!();
340//! # let authority_key: &Pubkey = todo!();
341//! # let mint_authority: &Pubkey = todo!();
342//! # let freeze_authority: &Pubkey = todo!();
343//! # let user: &AccountInfo = todo!();
344//! # let user_owner: &Pubkey = todo!();
345//! # let user_account: &AccountInfo = todo!();
346//! # let recipient_account: &AccountInfo = todo!();
347//! # let fee_vault: &AccountInfo = todo!();
348//!
349//! // Step 1: Calculate space needed for mint with TransferFeeConfig extension
350//! let extensions = &[ExtensionType::TransferFeeConfig];
351//! let mint_space = mint_space_for_extensions(extensions);
352//! // Result: 82 (base) + 3 (header) + 108 (fee config) = 193 bytes
353//!
354//! let rent_lamports = 0; /* calculate rent for mint_space */
355//!
356//! // Step 2: Create the mint account with correct size
357//! CreateAccount {
358//!     from: payer,
359//!     to: mint,
360//!     lamports: rent_lamports,
361//!     space: mint_space,
362//!     owner: &TOKEN_2022_PROGRAM_ID, // Must use Token-2022 for extensions!
363//! }.invoke()?;
364//!
365//! // Step 3: Initialize the TransferFee extension BEFORE initializing the mint
366//! InitializeTransferFeeConfig {
367//!     mint,
368//!     transfer_fee_config_authority: Some(authority_key),
369//!     withdraw_withheld_authority: Some(authority_key),
370//!     transfer_fee_basis_points: 100, // 1% fee (100 basis points)
371//!     maximum_fee: 5_000_000, // 0.005 token max fee (with 9 decimals)
372//! }.invoke()?;
373//!
374//! // Step 4: NOW initialize the mint (after extensions)
375//! InitializeMint2 {
376//!     mint,
377//!     mint_authority,
378//!     freeze_authority: Some(freeze_authority),
379//!     decimals: 9,
380//! }.invoke()?;
381//!
382//! // Step 5: Create token accounts (inherit TransferFeeAmount extension)
383//! let account_extensions = &[ExtensionType::TransferFeeConfig];
384//! let account_space = account_space_for_extensions(account_extensions);
385//! // Accounts automatically get TransferFeeAmount extension
386//! let account_rent = 0;
387//!
388//! CreateAccount {
389//!     from: payer,
390//!     to: user_account,
391//!     lamports: account_rent,
392//!     space: account_space,
393//!     owner: &TOKEN_2022_PROGRAM_ID,
394//! }.invoke()?;
395//!
396//! InitializeAccount3 {
397//!     account: user_account,
398//!     mint,
399//!     owner: user_owner,
400//! }.invoke()?;
401//!
402//! // Step 6: Transfer with fees
403//! use pinocchio_tkn::helpers::calculate_transfer_fee;
404//! let amount = 100_000_000; // 0.1 token
405//! let fee = calculate_transfer_fee(amount, 100, 5_000_000); // Calculate fee first
406//!
407//! TransferCheckedWithFee {
408//!     source: user_account,
409//!     mint,
410//!     destination: recipient_account,
411//!     authority: user,
412//!     amount,
413//!     decimals: 9,
414//!     fee, // Must provide calculated fee
415//! }.invoke()?;
416//!
417//! // Step 7: Withdraw collected fees (authority only)
418//! WithdrawWithheldTokensFromAccounts {
419//!     mint,
420//!     destination: fee_vault,
421//!     withdraw_withheld_authority: authority,
422//!     sources: &[user_account, recipient_account], // Accounts to withdraw from
423//! }.invoke()?;
424//! # Ok(())
425//! # }
426//! ```
427//!
428//! **Critical Order:**
429//! 1. Calculate space with extensions
430//! 2. Create account with correct size
431//! 3. Initialize extensions FIRST
432//! 4. Initialize mint LAST
433//!
434//! ### C. Token-2022 with Multiple Extensions (Advanced)
435//!
436//! Creating a premium token with Transfer Fees + Metadata + Mint Close Authority:
437//!
438//! ```rust,ignore
439//! # fn main() -> pinocchio::ProgramResult {
440//! # use pinocchio::account_info::AccountInfo;
441//! # use pinocchio::pubkey::Pubkey;
442//! use pinocchio_tkn::prelude::*;
443//! use pinocchio_tkn::helpers::{mint_space_for_extensions, ExtensionType, account_space_for_extensions};
444//! use pinocchio::system::CreateAccount;
445//! # let payer: &AccountInfo = todo!();
446//! # let mint: &AccountInfo = todo!();
447//! # let mint_key: &Pubkey = todo!();
448//! # let authority: &AccountInfo = todo!();
449//! # let authority_key: &Pubkey = todo!();
450//! # let mint_authority: &Pubkey = todo!();
451//! # let freeze_authority: &Pubkey = todo!();
452//! # let user: &AccountInfo = todo!();
453//! # let user_owner: &Pubkey = todo!();
454//! # let user_account: &AccountInfo = todo!();
455//!
456//! // Step 1: Calculate space for ALL extensions
457//! let extensions = &[
458//!     ExtensionType::TransferFeeConfig,    // 108 bytes
459//!     ExtensionType::MetadataPointer,      // 32 bytes
460//!     ExtensionType::MintCloseAuthority,   // 32 bytes
461//! ];
462//! let mint_space = mint_space_for_extensions(extensions);
463//! // = 82 + 3 + 108 + 32 + 32 = 257 bytes
464//!
465//! let rent_lamports = 0; /* calculate rent */
466//!
467//! // Step 2: Create mint account
468//! CreateAccount {
469//!     from: payer,
470//!     to: mint,
471//!     lamports: rent_lamports,
472//!     space: mint_space,
473//!     owner: &TOKEN_2022_PROGRAM_ID,
474//! }.invoke()?;
475//!
476//! // Step 3a: Initialize TransferFee extension
477//! InitializeTransferFeeConfig {
478//!     mint,
479//!     transfer_fee_config_authority: Some(authority_key),
480//!     withdraw_withheld_authority: Some(authority_key),
481//!     transfer_fee_basis_points: 50, // 0.5%
482//!     maximum_fee: 10_000_000,
483//! }.invoke()?;
484//!
485//! // Step 3b: Initialize MetadataPointer extension
486//! InitializeMetadataPointer {
487//!     mint,
488//!     authority: Some(authority_key),
489//!     metadata_address: Some(mint_key), // Store metadata on the mint itself
490//! }.invoke()?;
491//!
492//! // Step 3c: Initialize MintCloseAuthority extension
493//! InitializeMintCloseAuthority {
494//!     mint,
495//!     close_authority: Some(authority_key),
496//! }.invoke()?;
497//!
498//! // Step 4: Initialize the mint (AFTER all extensions)
499//! InitializeMint2 {
500//!     mint,
501//!     mint_authority,
502//!     freeze_authority: Some(freeze_authority),
503//!     decimals: 6, // USDC-style decimals
504//! }.invoke()?;
505//!
506//! // Step 5: Initialize on-mint metadata (AFTER mint initialization)
507//! InitializeTokenMetadata {
508//!     metadata: mint, // Using the mint itself as metadata storage
509//!     update_authority: authority,
510//!     mint,
511//!     mint_authority: authority,
512//!     name: "Premium Token",
513//!     symbol: "PREM",
514//!     uri: "https://example.com/premium-token.json",
515//! }.invoke()?;
516//!
517//! // Step 6: Create token accounts (with inherited extensions)
518//! let account_space = account_space_for_extensions(extensions);
519//! let account_rent = 0;
520//!
521//! CreateAccount {
522//!     from: payer,
523//!     to: user_account,
524//!     lamports: account_rent,
525//!     space: account_space,
526//!     owner: &TOKEN_2022_PROGRAM_ID,
527//! }.invoke()?;
528//!
529//! InitializeAccount3 {
530//!     account: user_account,
531//!     mint,
532//!     owner: user_owner,
533//! }.invoke()?;
534//!
535//! // Step 7: Use the token normally
536//! MintToChecked {
537//!     mint,
538//!     account: user_account,
539//!     mint_authority: authority,
540//!     amount: 1_000_000, // 1 PREM
541//!     decimals: 6,
542//! }.invoke()?;
543//!
544//! // Step 8: Later, close the mint when done (requires MintCloseAuthority)
545//! // First, burn all supply...
546//! // Then close the mint:
547//! CloseAccount {
548//!     account: mint,
549//!     destination: authority, // Rent refund destination
550//!     authority,
551//! }.invoke()?;
552//! # Ok(())
553//! # }
554//! ```
555//!
556//! **Extension Initialization Order Rules:**
557//!
558//! ```text
559//! ┌─────────────────────────────────────────────────────────┐
560//! │ BEFORE InitializeMint2:                                 │
561//! │ - TransferFeeConfig                                     │
562//! │ - MetadataPointer                                       │
563//! │ - GroupPointer                                          │
564//! │ - GroupMemberPointer                                    │
565//! │ - TransferHook                                          │
566//! │ - InterestBearingConfig                                 │
567//! │ - PermanentDelegate                                     │
568//! │ - NonTransferableMint                                   │
569//! │ - MintCloseAuthority                                    │
570//! │ - DefaultAccountState                                   │
571//! │ - Pausable                                              │
572//! │ - ScaledUiAmount                                        │
573//! └─────────────────────────────────────────────────────────┘
574//!                           ↓
575//! ┌─────────────────────────────────────────────────────────┐
576//! │ InitializeMint2 or InitializeMint                       │
577//! └─────────────────────────────────────────────────────────┘
578//!                           ↓
579//! ┌─────────────────────────────────────────────────────────┐
580//! │ AFTER InitializeMint2:                                  │
581//! │ - InitializeTokenMetadata (if using MetadataPointer)    │
582//! └─────────────────────────────────────────────────────────┘
583//! ```
584//!
585//! ### D. Common Patterns
586//!
587//! #### Detecting Which Program a Token Uses
588//!
589//! ```rust,ignore
590//! # use pinocchio::account_info::AccountInfo;
591//! use pinocchio_tkn::helpers::{is_token_2022, is_spl_token, has_extensions};
592//! # let mint_account: &AccountInfo = todo!();
593//!
594//! if is_token_2022(&mint_account) {
595//!     // This is a Token-2022 mint
596//!     if has_extensions(&mint_account, true) {
597//!         // Has extensions - might have fees, metadata, etc.
598//!         // Be careful with transfers!
599//!     } else {
600//!         // Token-2022 but no extensions - same as legacy
601//!     }
602//! } else if is_spl_token(&mint_account) {
603//!     // Legacy SPL Token - no extensions possible
604//! }
605//! ```
606//!
607//! #### Reading Token State and Extensions
608//!
609//! ```rust,ignore
610//! # fn main() -> pinocchio::ProgramResult {
611//! # use pinocchio::account_info::AccountInfo;
612//! # use pinocchio::program_error::ProgramError;
613//! use pinocchio_tkn::state::{Mint, TokenAccount};
614//! use pinocchio_tkn::helpers::has_extensions;
615//! # let mint_account: &AccountInfo = todo!();
616//! # let account: &AccountInfo = todo!();
617//!
618//! // Read mint state
619//! let mint = Mint::from_account_info(&mint_account)?;
620//! let supply = mint.supply();
621//! let decimals = mint.decimals();
622//!
623//! if let Some(authority) = mint.mint_authority() {
624//!     // Minting is enabled
625//! } else {
626//!     // Supply is fixed - no more minting possible
627//! }
628//!
629//! // Read token account state
630//! let token = TokenAccount::from_account_info(&account)?;
631//! let balance = token.amount();
632//! let owner = token.owner();
633//!
634//! if token.is_frozen() {
635//!     return Err(ProgramError::InvalidAccountData); // Cannot transfer
636//! }
637//!
638//! // Access extension data (Token-2022 only)
639//! if has_extensions(&mint_account, true) {
640//!     let raw_data = mint.raw_data();
641//!     // Extension data starts at byte 82
642//!     // Parse extension header and data manually if needed
643//! }
644//! # Ok(())
645//! # }
646//! ```
647//!
648//! #### Security Validation Pattern
649//!
650//! Always validate accounts before performing operations:
651//!
652//! ```rust,ignore
653//! # use pinocchio::account_info::AccountInfo;
654//! # use pinocchio::program_error::ProgramError;
655//! use pinocchio_tkn::helpers::*;
656//! use pinocchio_tkn::state::Mint;
657//! use pinocchio_tkn::TransferChecked;
658//!
659//! fn secure_transfer(
660//!     source: &AccountInfo,
661//!     destination: &AccountInfo,
662//!     mint: &AccountInfo,
663//!     authority: &AccountInfo,
664//!     amount: u64,
665//! ) -> Result<(), ProgramError> {
666//!     // 1. Validate ownership
667//!     let program_id = get_token_program_id(mint);
668//!     assert_owned_by(source, program_id)?;
669//!     assert_owned_by(destination, program_id)?;
670//!     assert_owned_by(mint, program_id)?;
671//!
672//!     // 2. Validate account types
673//!     assert_is_mint(mint)?;
674//!     assert_is_token_account(source, Some(mint.key()), None)?;
675//!     assert_is_token_account(destination, Some(mint.key()), None)?;
676//!
677//!     // 3. Validate state
678//!     assert_account_not_frozen(source)?;
679//!     assert_account_not_frozen(destination)?;
680//!
681//!     // 4. Validate authority
682//!     assert_token_account_owner(source, authority)?;
683//!
684//!     // 5. All checks passed - safe to transfer
685//!     TransferChecked {
686//!         source,
687//!         mint,
688//!         destination,
689//!         authority,
690//!         amount,
691//!         decimals: Mint::from_account_info(mint)?.decimals(),
692//!         program_id: None,
693//!     }.invoke()?;
694//!
695//!     Ok(())
696//! }
697//! ```
698//!
699//! #### Fee Calculations
700//!
701//! ```rust,ignore
702//! use pinocchio_tkn::helpers::{calculate_transfer_fee, calculate_inverse_transfer_fee};
703//!
704//! // Scenario 1: User wants to send 100 tokens, how much fee?
705//! let amount = 100_000_000; // 100 tokens (6 decimals)
706//! let fee_bps = 100; // 1%
707//! let max_fee = 5_000_000; // 5 tokens max
708//!
709//! let fee = calculate_transfer_fee(amount, fee_bps, max_fee);
710//! // fee = 1_000_000 (1 token)
711//! // Recipient receives: 100_000_000 - 1_000_000 = 99_000_000
712//!
713//! // Scenario 2: User wants recipient to receive EXACTLY 100 tokens, how much to send?
714//! let desired_amount = 100_000_000;
715//! let (amount_to_send, fee) = calculate_inverse_transfer_fee(
716//!     desired_amount,
717//!     fee_bps,
718//!     max_fee,
719//! );
720//! // amount_to_send = 101_010_101
721//! // fee = 1_010_101
722//! // Recipient receives: 101_010_101 - 1_010_101 = 100_000_000 ✓
723//! ```
724//!
725//! ## Examples
726//!
727//! Check the `examples/` directory for complete working examples:
728//!
729//! - `01_basic_transfer.rs` - Simple SPL Token and Token-2022 transfers
730//! - `02_transfer_fees.rs` - Setting up and using transfer fees
731//! - `03_onchain_metadata.rs` - Adding metadata to tokens
732//! - `04_full_workflow.rs` - Complete token lifecycle
733//! - `05_validation_security.rs` - Security best practices
734//!
735//! ## Program IDs
736//!
737//! The crate provides constants for both token programs:
738//!
739//! - [`TOKEN_PROGRAM_ID`]: SPL Token (legacy) - `TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA`
740//! - [`TOKEN_2022_PROGRAM_ID`]: Token-2022 - `TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb`
741//! - [`ID`]: Alias for Token-2022 (default)
742//!
743//! ## Compatibility
744//!
745//! - **Rust Version**: 1.79 or later
746//! - **no_std**: Fully compatible with no_std environments
747//! - **Pinocchio**: Built on pinocchio 0.9+ for zero-allocation runtime
748//! - **Solana**: Compatible with Solana SDK 3.0+
749
750#![no_std]
751
752use core::mem::MaybeUninit;
753use pinocchio::pubkey::Pubkey;
754use pinocchio_pubkey::pubkey;
755
756/// SPL Token (legacy) Program ID: `TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA`
757pub const TOKEN_PROGRAM_ID: Pubkey = pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
758
759/// Token-2022 Program ID: `TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb`
760pub const TOKEN_2022_PROGRAM_ID: Pubkey = pubkey!("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb");
761
762/// Alias for Token-2022 (default, backward compatibility)
763pub const ID: Pubkey = TOKEN_2022_PROGRAM_ID;
764
765/// Common instructions (work with both SPL Token and Token-2022)
766#[cfg(feature = "common")]
767pub mod common;
768
769/// Token-2022 exclusive extensions
770#[cfg(any(
771    feature = "ext-transfer-fee",
772    feature = "ext-metadata",
773    feature = "ext-interest",
774    feature = "ext-pointers",
775    feature = "ext-misc"
776))]
777pub mod extensions;
778
779/// Account state parsing (Mint, TokenAccount)
780#[cfg(feature = "state")]
781pub mod state;
782
783/// Utility functions (calculations, validation, detection)
784#[cfg(feature = "helpers")]
785pub mod helpers;
786
787/// Legacy module re-exports (backward compatibility)
788#[deprecated(since = "0.2.0", note = "Use `common` or `extensions` modules instead")]
789pub mod instructions {
790    #[cfg(feature = "common")]
791    pub use crate::common::*;
792
793    #[cfg(any(
794        feature = "ext-transfer-fee",
795        feature = "ext-metadata",
796        feature = "ext-interest",
797        feature = "ext-pointers",
798        feature = "ext-misc"
799    ))]
800    pub use crate::extensions::*;
801}
802
803/// Convenient prelude with all common types
804pub mod prelude {
805    #[cfg(feature = "common")]
806    pub use crate::common::*;
807
808    #[cfg(any(
809        feature = "ext-transfer-fee",
810        feature = "ext-metadata",
811        feature = "ext-interest",
812        feature = "ext-pointers",
813        feature = "ext-misc"
814    ))]
815    pub use crate::extensions::*;
816
817    #[cfg(feature = "state")]
818    pub use crate::state::{AccountState, Mint, TokenAccount, MINT_SIZE, TOKEN_ACCOUNT_SIZE};
819
820    #[cfg(feature = "helpers")]
821    pub use crate::helpers::*;
822
823    pub use crate::{ID, TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID};
824}
825
826#[macro_use]
827pub mod macros;
828
829// Internal helpers for instruction building
830const UNINIT_BYTE: MaybeUninit<u8> = MaybeUninit::<u8>::uninit();
831
832#[inline(always)]
833fn write_bytes(destination: &mut [MaybeUninit<u8>], source: &[u8]) {
834    for (d, s) in destination.iter_mut().zip(source.iter()) {
835        d.write(*s);
836    }
837}