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}