diff --git a/js/token-idl/.prettierignore b/js/token-idl/.prettierignore new file mode 100644 index 0000000000..00b694961e --- /dev/null +++ b/js/token-idl/.prettierignore @@ -0,0 +1,4 @@ +node_modules +dist +build +coverage diff --git a/js/token-idl/.prettierrc b/js/token-idl/.prettierrc new file mode 100644 index 0000000000..59be93e26f --- /dev/null +++ b/js/token-idl/.prettierrc @@ -0,0 +1,10 @@ +{ + "semi": true, + "trailingComma": "all", + "singleQuote": true, + "printWidth": 80, + "useTabs": false, + "tabWidth": 4, + "bracketSpacing": true, + "arrowParens": "avoid" +} diff --git a/js/token-idl/eslint.config.cjs b/js/token-idl/eslint.config.cjs new file mode 100644 index 0000000000..66152ccf0a --- /dev/null +++ b/js/token-idl/eslint.config.cjs @@ -0,0 +1,66 @@ +const js = require('@eslint/js'); +const tseslint = require('@typescript-eslint/eslint-plugin'); +const tsParser = require('@typescript-eslint/parser'); + +module.exports = [ + { + ignores: [ + 'node_modules/**', + 'dist/**', + 'build/**', + '*.config.js', + 'eslint.config.js', + 'eslint.config.cjs', + ], + }, + js.configs.recommended, + { + files: ['**/*.js', '**/*.cjs', '**/*.mjs'], + languageOptions: { + ecmaVersion: 2022, + sourceType: 'module', + globals: { + require: 'readonly', + module: 'readonly', + process: 'readonly', + __dirname: 'readonly', + __filename: 'readonly', + exports: 'readonly', + console: 'readonly', + Buffer: 'readonly', + }, + }, + }, + { + files: ['src/**/*.ts', 'scripts/**/*.ts'], + languageOptions: { + parser: tsParser, + parserOptions: { + ecmaVersion: 2022, + sourceType: 'module', + }, + globals: { + process: 'readonly', + console: 'readonly', + __dirname: 'readonly', + __filename: 'readonly', + Buffer: 'readonly', + }, + }, + plugins: { + '@typescript-eslint': tseslint, + }, + rules: { + ...tseslint.configs.recommended.rules, + '@typescript-eslint/ban-ts-comment': 0, + '@typescript-eslint/no-explicit-any': 0, + '@typescript-eslint/no-var-requires': 0, + '@typescript-eslint/no-unused-vars': 0, + '@typescript-eslint/no-require-imports': 0, + 'no-prototype-builtins': 0, + 'no-undef': 0, + 'no-unused-vars': 0, + 'no-redeclare': 0, + }, + }, +]; diff --git a/js/token-idl/package.json b/js/token-idl/package.json new file mode 100644 index 0000000000..63adc03804 --- /dev/null +++ b/js/token-idl/package.json @@ -0,0 +1,27 @@ +{ + "name": "@lightprotocol/token-idl", + "version": "0.1.0", + "description": "Light Protocol Token IDL and Codama client generation", + "type": "module", + "private": true, + "scripts": { + "generate": "tsx scripts/generate-clients.ts", + "build": "pnpm run generate", + "lint": "eslint .", + "format": "prettier --write ." + }, + "devDependencies": { + "@codama/nodes": "^1.4.1", + "@codama/renderers-js": "^1.2.8", + "@codama/visitors": "^1.4.1", + "@codama/visitors-core": "^1.4.1", + "@eslint/js": "9.36.0", + "@typescript-eslint/eslint-plugin": "^8.44.0", + "@typescript-eslint/parser": "^8.44.0", + "codama": "^1.4.1", + "eslint": "^9.36.0", + "prettier": "^3.3.3", + "tsx": "^4.19.2", + "typescript": "^5.7.3" + } +} diff --git a/js/token-idl/scripts/generate-clients.ts b/js/token-idl/scripts/generate-clients.ts new file mode 100644 index 0000000000..dbc0240c96 --- /dev/null +++ b/js/token-idl/scripts/generate-clients.ts @@ -0,0 +1,57 @@ +/** + * Generate TypeScript clients from the Light Token IDL using Codama. + */ + +import { createFromRoot } from 'codama'; +import { renderJavaScriptVisitor } from '@codama/renderers-js'; +import { setInstructionAccountDefaultValuesVisitor } from '@codama/visitors'; +import { publicKeyValueNode } from 'codama'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +import { + lightTokenIdl, + LIGHT_TOKEN_PROGRAM_ID, + SYSTEM_PROGRAM, +} from '../src/idl.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// Output directory for generated TypeScript +const typescriptOutputDir = path.resolve( + __dirname, + '../../token-sdk/src/generated', +); + +console.log('Creating Codama instance from Light Token IDL...'); +const codama = createFromRoot(lightTokenIdl); + +// Apply default account values for common accounts +console.log('Applying default account values...'); +codama.update( + setInstructionAccountDefaultValuesVisitor([ + { + account: 'systemProgram', + defaultValue: publicKeyValueNode(SYSTEM_PROGRAM), + }, + { + account: 'selfProgram', + defaultValue: publicKeyValueNode(LIGHT_TOKEN_PROGRAM_ID), + }, + ]), +); + +// Generate TypeScript client +console.log(`Generating TypeScript client to ${typescriptOutputDir}...`); +codama.accept( + renderJavaScriptVisitor(typescriptOutputDir, { + formatCode: true, + dependencyMap: { + // Map codama codecs to @solana/codecs + generatedPackage: '@lightprotocol/token-sdk', + }, + }), +); + +console.log('Generation complete!'); diff --git a/js/token-idl/src/idl.ts b/js/token-idl/src/idl.ts new file mode 100644 index 0000000000..bf6d098637 --- /dev/null +++ b/js/token-idl/src/idl.ts @@ -0,0 +1,1189 @@ +/** + * Light Protocol Token IDL + * + * Programmatic IDL definition for the Light Token program using Codama. + * The program uses single-byte SPL-compatible discriminators (3-18) and + * custom discriminators (100+) with Pinocchio-based instruction dispatch. + */ + +import { + rootNode, + programNode, + instructionNode, + instructionAccountNode, + instructionArgumentNode, + pdaNode, + pdaValueNode, + pdaLinkNode, + constantDiscriminatorNode, + constantValueNode, + constantPdaSeedNodeFromString, + variablePdaSeedNode, + numberTypeNode, + numberValueNode, + publicKeyTypeNode, + publicKeyValueNode, + booleanTypeNode, + optionTypeNode, + bytesTypeNode, + structTypeNode, + structFieldTypeNode, + arrayTypeNode, + fixedSizeTypeNode, +} from 'codama'; + +// ============================================================================ +// CONSTANTS +// ============================================================================ + +export const LIGHT_TOKEN_PROGRAM_ID = + 'cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m'; +export const CPI_AUTHORITY = 'GXtd2izAiMJPwMEjfgTRH3d7k9mjn4Jq3JrWFv9gySYy'; +export const MINT_ADDRESS_TREE = 'amt2kaJA14v3urZbZvnc5v2np8jqvc4Z8zDep5wbtzx'; +export const SYSTEM_PROGRAM = '11111111111111111111111111111111'; + +// ============================================================================ +// INSTRUCTION DISCRIMINATORS +// ============================================================================ + +/** SPL-compatible discriminators */ +export const DISCRIMINATOR = { + TRANSFER: 3, + APPROVE: 4, + REVOKE: 5, + MINT_TO: 7, + BURN: 8, + CLOSE: 9, + FREEZE: 10, + THAW: 11, + TRANSFER_CHECKED: 12, + MINT_TO_CHECKED: 14, + BURN_CHECKED: 15, + CREATE_TOKEN_ACCOUNT: 18, + CREATE_ATA: 100, + TRANSFER2: 101, + CREATE_ATA_IDEMPOTENT: 102, + MINT_ACTION: 103, +} as const; + +// ============================================================================ +// TYPE DEFINITIONS +// ============================================================================ + +/** Compression mode enum for Transfer2 */ +const compressionModeType = numberTypeNode('u8'); + +/** Compression struct for Transfer2 */ +const compressionStructType = structTypeNode([ + structFieldTypeNode({ name: 'mode', type: numberTypeNode('u8') }), + structFieldTypeNode({ name: 'amount', type: numberTypeNode('u64') }), + structFieldTypeNode({ name: 'mint', type: numberTypeNode('u8') }), + structFieldTypeNode({ + name: 'sourceOrRecipient', + type: numberTypeNode('u8'), + }), + structFieldTypeNode({ name: 'authority', type: numberTypeNode('u8') }), + structFieldTypeNode({ + name: 'poolAccountIndex', + type: numberTypeNode('u8'), + }), + structFieldTypeNode({ name: 'poolIndex', type: numberTypeNode('u8') }), + structFieldTypeNode({ name: 'bump', type: numberTypeNode('u8') }), + structFieldTypeNode({ name: 'decimals', type: numberTypeNode('u8') }), +]); + +/** Packed merkle context */ +const packedMerkleContextType = structTypeNode([ + structFieldTypeNode({ + name: 'merkleTreePubkeyIndex', + type: numberTypeNode('u8'), + }), + structFieldTypeNode({ + name: 'queuePubkeyIndex', + type: numberTypeNode('u8'), + }), + structFieldTypeNode({ name: 'leafIndex', type: numberTypeNode('u32') }), + structFieldTypeNode({ name: 'proveByIndex', type: booleanTypeNode() }), +]); + +/** Input token data with context */ +const multiInputTokenDataType = structTypeNode([ + structFieldTypeNode({ name: 'owner', type: numberTypeNode('u8') }), + structFieldTypeNode({ name: 'amount', type: numberTypeNode('u64') }), + structFieldTypeNode({ name: 'hasDelegate', type: booleanTypeNode() }), + structFieldTypeNode({ name: 'delegate', type: numberTypeNode('u8') }), + structFieldTypeNode({ name: 'mint', type: numberTypeNode('u8') }), + structFieldTypeNode({ name: 'version', type: numberTypeNode('u8') }), + structFieldTypeNode({ + name: 'merkleContext', + type: packedMerkleContextType, + }), + structFieldTypeNode({ name: 'rootIndex', type: numberTypeNode('u16') }), +]); + +/** Output token data */ +const multiTokenOutputDataType = structTypeNode([ + structFieldTypeNode({ name: 'owner', type: numberTypeNode('u8') }), + structFieldTypeNode({ name: 'amount', type: numberTypeNode('u64') }), + structFieldTypeNode({ name: 'hasDelegate', type: booleanTypeNode() }), + structFieldTypeNode({ name: 'delegate', type: numberTypeNode('u8') }), + structFieldTypeNode({ name: 'mint', type: numberTypeNode('u8') }), + structFieldTypeNode({ name: 'version', type: numberTypeNode('u8') }), +]); + +/** CPI context */ +const cpiContextType = structTypeNode([ + structFieldTypeNode({ name: 'setContext', type: booleanTypeNode() }), + structFieldTypeNode({ name: 'firstSetContext', type: booleanTypeNode() }), + structFieldTypeNode({ + name: 'cpiContextAccountIndex', + type: numberTypeNode('u8'), + }), +]); + +/** Compressible extension instruction data */ +const compressibleExtensionDataType = structTypeNode([ + structFieldTypeNode({ + name: 'tokenAccountVersion', + type: numberTypeNode('u8'), + }), + structFieldTypeNode({ name: 'rentPayment', type: numberTypeNode('u16') }), + structFieldTypeNode({ + name: 'compressionOnly', + type: numberTypeNode('u8'), + }), + structFieldTypeNode({ name: 'writeTopUp', type: numberTypeNode('u32') }), + structFieldTypeNode({ + name: 'compressToPubkey', + type: optionTypeNode( + structTypeNode([ + structFieldTypeNode({ + name: 'bump', + type: numberTypeNode('u8'), + }), + structFieldTypeNode({ + name: 'programId', + type: fixedSizeTypeNode(bytesTypeNode(), 32), + }), + structFieldTypeNode({ + name: 'seeds', + type: arrayTypeNode(bytesTypeNode()), + }), + ]), + ), + }), +]); + +// ============================================================================ +// IDL ROOT +// ============================================================================ + +export const lightTokenIdl = rootNode( + programNode({ + name: 'lightToken', + publicKey: LIGHT_TOKEN_PROGRAM_ID, + version: '1.0.0', + docs: ['Light Protocol compressed token program'], + + // ======================================================================== + // PDAs + // ======================================================================== + pdas: [ + pdaNode({ + name: 'associatedTokenAccount', + seeds: [ + variablePdaSeedNode('owner', publicKeyTypeNode()), + constantPdaSeedNodeFromString( + 'utf8', + LIGHT_TOKEN_PROGRAM_ID, + ), + variablePdaSeedNode('mint', publicKeyTypeNode()), + ], + docs: [ + 'Associated token account PDA: [owner, LIGHT_TOKEN_PROGRAM_ID, mint]', + ], + }), + pdaNode({ + name: 'lightMint', + seeds: [ + constantPdaSeedNodeFromString('utf8', 'compressed_mint'), + variablePdaSeedNode('mintSigner', publicKeyTypeNode()), + ], + docs: ['Light mint PDA: ["compressed_mint", mintSigner]'], + }), + pdaNode({ + name: 'splInterfacePool', + seeds: [ + constantPdaSeedNodeFromString('utf8', 'pool'), + variablePdaSeedNode('mint', publicKeyTypeNode()), + ], + docs: ['SPL interface pool PDA: ["pool", mint]'], + }), + ], + + // ======================================================================== + // ACCOUNTS (for generated types) + // ======================================================================== + accounts: [], + + // ======================================================================== + // INSTRUCTIONS + // ======================================================================== + instructions: [ + // ---------------------------------------------------------------------- + // CToken Transfer (discriminator: 3) + // ---------------------------------------------------------------------- + instructionNode({ + name: 'ctokenTransfer', + discriminators: [ + constantDiscriminatorNode( + constantValueNode( + numberTypeNode('u8'), + numberValueNode(DISCRIMINATOR.TRANSFER), + ), + ), + ], + docs: ['Transfer CToken between decompressed accounts'], + accounts: [ + instructionAccountNode({ + name: 'source', + isSigner: false, + isWritable: true, + docs: ['Source CToken account'], + }), + instructionAccountNode({ + name: 'destination', + isSigner: false, + isWritable: true, + docs: ['Destination CToken account'], + }), + instructionAccountNode({ + name: 'authority', + isSigner: true, + isWritable: false, + docs: ['Authority (owner or delegate)'], + }), + instructionAccountNode({ + name: 'systemProgram', + isSigner: false, + isWritable: false, + defaultValue: publicKeyValueNode(SYSTEM_PROGRAM), + docs: ['System program for rent top-up'], + }), + instructionAccountNode({ + name: 'feePayer', + isSigner: true, + isWritable: true, + isOptional: true, + docs: ['Optional fee payer for rent top-ups'], + }), + ], + arguments: [ + instructionArgumentNode({ + name: 'discriminator', + type: numberTypeNode('u8'), + defaultValue: numberValueNode(DISCRIMINATOR.TRANSFER), + defaultValueStrategy: 'omitted', + }), + instructionArgumentNode({ + name: 'amount', + type: numberTypeNode('u64'), + docs: ['Amount to transfer'], + }), + instructionArgumentNode({ + name: 'maxTopUp', + type: optionTypeNode(numberTypeNode('u16')), + docs: [ + 'Maximum lamports for rent top-up (0 = no limit)', + ], + }), + ], + }), + + // ---------------------------------------------------------------------- + // CToken TransferChecked (discriminator: 12) + // ---------------------------------------------------------------------- + instructionNode({ + name: 'ctokenTransferChecked', + discriminators: [ + constantDiscriminatorNode( + constantValueNode( + numberTypeNode('u8'), + numberValueNode(DISCRIMINATOR.TRANSFER_CHECKED), + ), + ), + ], + docs: ['Transfer CToken with decimals validation'], + accounts: [ + instructionAccountNode({ + name: 'source', + isSigner: false, + isWritable: true, + }), + instructionAccountNode({ + name: 'mint', + isSigner: false, + isWritable: false, + }), + instructionAccountNode({ + name: 'destination', + isSigner: false, + isWritable: true, + }), + instructionAccountNode({ + name: 'authority', + isSigner: true, + isWritable: false, + }), + instructionAccountNode({ + name: 'systemProgram', + isSigner: false, + isWritable: false, + defaultValue: publicKeyValueNode(SYSTEM_PROGRAM), + }), + ], + arguments: [ + instructionArgumentNode({ + name: 'discriminator', + type: numberTypeNode('u8'), + defaultValue: numberValueNode( + DISCRIMINATOR.TRANSFER_CHECKED, + ), + defaultValueStrategy: 'omitted', + }), + instructionArgumentNode({ + name: 'amount', + type: numberTypeNode('u64'), + }), + instructionArgumentNode({ + name: 'decimals', + type: numberTypeNode('u8'), + }), + instructionArgumentNode({ + name: 'maxTopUp', + type: optionTypeNode(numberTypeNode('u16')), + }), + ], + }), + + // ---------------------------------------------------------------------- + // CToken Approve (discriminator: 4) + // ---------------------------------------------------------------------- + instructionNode({ + name: 'ctokenApprove', + discriminators: [ + constantDiscriminatorNode( + constantValueNode( + numberTypeNode('u8'), + numberValueNode(DISCRIMINATOR.APPROVE), + ), + ), + ], + docs: ['Approve delegate on decompressed CToken account'], + accounts: [ + instructionAccountNode({ + name: 'tokenAccount', + isSigner: false, + isWritable: true, + }), + instructionAccountNode({ + name: 'delegate', + isSigner: false, + isWritable: false, + }), + instructionAccountNode({ + name: 'owner', + isSigner: true, + isWritable: false, + }), + ], + arguments: [ + instructionArgumentNode({ + name: 'discriminator', + type: numberTypeNode('u8'), + defaultValue: numberValueNode(DISCRIMINATOR.APPROVE), + defaultValueStrategy: 'omitted', + }), + instructionArgumentNode({ + name: 'amount', + type: numberTypeNode('u64'), + }), + ], + }), + + // ---------------------------------------------------------------------- + // CToken Revoke (discriminator: 5) + // ---------------------------------------------------------------------- + instructionNode({ + name: 'ctokenRevoke', + discriminators: [ + constantDiscriminatorNode( + constantValueNode( + numberTypeNode('u8'), + numberValueNode(DISCRIMINATOR.REVOKE), + ), + ), + ], + docs: ['Revoke delegate on decompressed CToken account'], + accounts: [ + instructionAccountNode({ + name: 'tokenAccount', + isSigner: false, + isWritable: true, + }), + instructionAccountNode({ + name: 'owner', + isSigner: true, + isWritable: false, + }), + ], + arguments: [ + instructionArgumentNode({ + name: 'discriminator', + type: numberTypeNode('u8'), + defaultValue: numberValueNode(DISCRIMINATOR.REVOKE), + defaultValueStrategy: 'omitted', + }), + ], + }), + + // ---------------------------------------------------------------------- + // CToken MintTo (discriminator: 7) + // ---------------------------------------------------------------------- + instructionNode({ + name: 'ctokenMintTo', + discriminators: [ + constantDiscriminatorNode( + constantValueNode( + numberTypeNode('u8'), + numberValueNode(DISCRIMINATOR.MINT_TO), + ), + ), + ], + docs: ['Mint tokens to decompressed CToken account'], + accounts: [ + instructionAccountNode({ + name: 'mint', + isSigner: false, + isWritable: true, + }), + instructionAccountNode({ + name: 'tokenAccount', + isSigner: false, + isWritable: true, + }), + instructionAccountNode({ + name: 'mintAuthority', + isSigner: true, + isWritable: false, + }), + ], + arguments: [ + instructionArgumentNode({ + name: 'discriminator', + type: numberTypeNode('u8'), + defaultValue: numberValueNode(DISCRIMINATOR.MINT_TO), + defaultValueStrategy: 'omitted', + }), + instructionArgumentNode({ + name: 'amount', + type: numberTypeNode('u64'), + }), + ], + }), + + // ---------------------------------------------------------------------- + // CToken MintToChecked (discriminator: 14) + // ---------------------------------------------------------------------- + instructionNode({ + name: 'ctokenMintToChecked', + discriminators: [ + constantDiscriminatorNode( + constantValueNode( + numberTypeNode('u8'), + numberValueNode(DISCRIMINATOR.MINT_TO_CHECKED), + ), + ), + ], + docs: ['Mint tokens with decimals validation'], + accounts: [ + instructionAccountNode({ + name: 'mint', + isSigner: false, + isWritable: true, + }), + instructionAccountNode({ + name: 'tokenAccount', + isSigner: false, + isWritable: true, + }), + instructionAccountNode({ + name: 'mintAuthority', + isSigner: true, + isWritable: false, + }), + ], + arguments: [ + instructionArgumentNode({ + name: 'discriminator', + type: numberTypeNode('u8'), + defaultValue: numberValueNode( + DISCRIMINATOR.MINT_TO_CHECKED, + ), + defaultValueStrategy: 'omitted', + }), + instructionArgumentNode({ + name: 'amount', + type: numberTypeNode('u64'), + }), + instructionArgumentNode({ + name: 'decimals', + type: numberTypeNode('u8'), + }), + ], + }), + + // ---------------------------------------------------------------------- + // CToken Burn (discriminator: 8) + // ---------------------------------------------------------------------- + instructionNode({ + name: 'ctokenBurn', + discriminators: [ + constantDiscriminatorNode( + constantValueNode( + numberTypeNode('u8'), + numberValueNode(DISCRIMINATOR.BURN), + ), + ), + ], + docs: ['Burn tokens from decompressed CToken account'], + accounts: [ + instructionAccountNode({ + name: 'tokenAccount', + isSigner: false, + isWritable: true, + }), + instructionAccountNode({ + name: 'mint', + isSigner: false, + isWritable: true, + }), + instructionAccountNode({ + name: 'authority', + isSigner: true, + isWritable: false, + }), + ], + arguments: [ + instructionArgumentNode({ + name: 'discriminator', + type: numberTypeNode('u8'), + defaultValue: numberValueNode(DISCRIMINATOR.BURN), + defaultValueStrategy: 'omitted', + }), + instructionArgumentNode({ + name: 'amount', + type: numberTypeNode('u64'), + }), + ], + }), + + // ---------------------------------------------------------------------- + // CToken BurnChecked (discriminator: 15) + // ---------------------------------------------------------------------- + instructionNode({ + name: 'ctokenBurnChecked', + discriminators: [ + constantDiscriminatorNode( + constantValueNode( + numberTypeNode('u8'), + numberValueNode(DISCRIMINATOR.BURN_CHECKED), + ), + ), + ], + docs: ['Burn tokens with decimals validation'], + accounts: [ + instructionAccountNode({ + name: 'tokenAccount', + isSigner: false, + isWritable: true, + }), + instructionAccountNode({ + name: 'mint', + isSigner: false, + isWritable: true, + }), + instructionAccountNode({ + name: 'authority', + isSigner: true, + isWritable: false, + }), + ], + arguments: [ + instructionArgumentNode({ + name: 'discriminator', + type: numberTypeNode('u8'), + defaultValue: numberValueNode( + DISCRIMINATOR.BURN_CHECKED, + ), + defaultValueStrategy: 'omitted', + }), + instructionArgumentNode({ + name: 'amount', + type: numberTypeNode('u64'), + }), + instructionArgumentNode({ + name: 'decimals', + type: numberTypeNode('u8'), + }), + ], + }), + + // ---------------------------------------------------------------------- + // CToken Close (discriminator: 9) + // ---------------------------------------------------------------------- + instructionNode({ + name: 'ctokenClose', + discriminators: [ + constantDiscriminatorNode( + constantValueNode( + numberTypeNode('u8'), + numberValueNode(DISCRIMINATOR.CLOSE), + ), + ), + ], + docs: ['Close decompressed CToken account'], + accounts: [ + instructionAccountNode({ + name: 'tokenAccount', + isSigner: false, + isWritable: true, + }), + instructionAccountNode({ + name: 'destination', + isSigner: false, + isWritable: true, + }), + instructionAccountNode({ + name: 'owner', + isSigner: true, + isWritable: false, + }), + ], + arguments: [ + instructionArgumentNode({ + name: 'discriminator', + type: numberTypeNode('u8'), + defaultValue: numberValueNode(DISCRIMINATOR.CLOSE), + defaultValueStrategy: 'omitted', + }), + ], + }), + + // ---------------------------------------------------------------------- + // CToken Freeze (discriminator: 10) + // ---------------------------------------------------------------------- + instructionNode({ + name: 'ctokenFreeze', + discriminators: [ + constantDiscriminatorNode( + constantValueNode( + numberTypeNode('u8'), + numberValueNode(DISCRIMINATOR.FREEZE), + ), + ), + ], + docs: ['Freeze decompressed CToken account'], + accounts: [ + instructionAccountNode({ + name: 'tokenAccount', + isSigner: false, + isWritable: true, + }), + instructionAccountNode({ + name: 'mint', + isSigner: false, + isWritable: false, + }), + instructionAccountNode({ + name: 'freezeAuthority', + isSigner: true, + isWritable: false, + }), + ], + arguments: [ + instructionArgumentNode({ + name: 'discriminator', + type: numberTypeNode('u8'), + defaultValue: numberValueNode(DISCRIMINATOR.FREEZE), + defaultValueStrategy: 'omitted', + }), + ], + }), + + // ---------------------------------------------------------------------- + // CToken Thaw (discriminator: 11) + // ---------------------------------------------------------------------- + instructionNode({ + name: 'ctokenThaw', + discriminators: [ + constantDiscriminatorNode( + constantValueNode( + numberTypeNode('u8'), + numberValueNode(DISCRIMINATOR.THAW), + ), + ), + ], + docs: ['Thaw frozen decompressed CToken account'], + accounts: [ + instructionAccountNode({ + name: 'tokenAccount', + isSigner: false, + isWritable: true, + }), + instructionAccountNode({ + name: 'mint', + isSigner: false, + isWritable: false, + }), + instructionAccountNode({ + name: 'freezeAuthority', + isSigner: true, + isWritable: false, + }), + ], + arguments: [ + instructionArgumentNode({ + name: 'discriminator', + type: numberTypeNode('u8'), + defaultValue: numberValueNode(DISCRIMINATOR.THAW), + defaultValueStrategy: 'omitted', + }), + ], + }), + + // ---------------------------------------------------------------------- + // Create Token Account (discriminator: 18) + // ---------------------------------------------------------------------- + instructionNode({ + name: 'createTokenAccount', + discriminators: [ + constantDiscriminatorNode( + constantValueNode( + numberTypeNode('u8'), + numberValueNode(DISCRIMINATOR.CREATE_TOKEN_ACCOUNT), + ), + ), + ], + docs: [ + 'Create CToken account (equivalent to SPL InitializeAccount3)', + ], + accounts: [ + instructionAccountNode({ + name: 'owner', + isSigner: false, + isWritable: false, + }), + instructionAccountNode({ + name: 'mint', + isSigner: false, + isWritable: false, + }), + instructionAccountNode({ + name: 'payer', + isSigner: true, + isWritable: true, + }), + instructionAccountNode({ + name: 'tokenAccount', + isSigner: false, + isWritable: true, + }), + instructionAccountNode({ + name: 'systemProgram', + isSigner: false, + isWritable: false, + defaultValue: publicKeyValueNode(SYSTEM_PROGRAM), + }), + instructionAccountNode({ + name: 'compressibleConfig', + isSigner: false, + isWritable: false, + isOptional: true, + }), + instructionAccountNode({ + name: 'rentSponsor', + isSigner: false, + isWritable: true, + isOptional: true, + }), + ], + arguments: [ + instructionArgumentNode({ + name: 'discriminator', + type: numberTypeNode('u8'), + defaultValue: numberValueNode( + DISCRIMINATOR.CREATE_TOKEN_ACCOUNT, + ), + defaultValueStrategy: 'omitted', + }), + instructionArgumentNode({ + name: 'compressibleConfig', + type: optionTypeNode(compressibleExtensionDataType), + }), + ], + }), + + // ---------------------------------------------------------------------- + // Create Associated Token Account (discriminator: 100) + // ---------------------------------------------------------------------- + instructionNode({ + name: 'createAssociatedTokenAccount', + discriminators: [ + constantDiscriminatorNode( + constantValueNode( + numberTypeNode('u8'), + numberValueNode(DISCRIMINATOR.CREATE_ATA), + ), + ), + ], + docs: ['Create associated CToken account'], + accounts: [ + instructionAccountNode({ + name: 'owner', + isSigner: false, + isWritable: false, + }), + instructionAccountNode({ + name: 'mint', + isSigner: false, + isWritable: false, + }), + instructionAccountNode({ + name: 'payer', + isSigner: true, + isWritable: true, + }), + instructionAccountNode({ + name: 'associatedTokenAccount', + isSigner: false, + isWritable: true, + defaultValue: pdaValueNode( + pdaLinkNode('associatedTokenAccount'), + ), + }), + instructionAccountNode({ + name: 'systemProgram', + isSigner: false, + isWritable: false, + defaultValue: publicKeyValueNode(SYSTEM_PROGRAM), + }), + instructionAccountNode({ + name: 'compressibleConfig', + isSigner: false, + isWritable: false, + }), + instructionAccountNode({ + name: 'rentSponsor', + isSigner: false, + isWritable: true, + }), + ], + arguments: [ + instructionArgumentNode({ + name: 'discriminator', + type: numberTypeNode('u8'), + defaultValue: numberValueNode(DISCRIMINATOR.CREATE_ATA), + defaultValueStrategy: 'omitted', + }), + instructionArgumentNode({ + name: 'bump', + type: numberTypeNode('u8'), + }), + instructionArgumentNode({ + name: 'compressibleConfig', + type: optionTypeNode(compressibleExtensionDataType), + }), + ], + }), + + // ---------------------------------------------------------------------- + // Create Associated Token Account Idempotent (discriminator: 102) + // ---------------------------------------------------------------------- + instructionNode({ + name: 'createAssociatedTokenAccountIdempotent', + discriminators: [ + constantDiscriminatorNode( + constantValueNode( + numberTypeNode('u8'), + numberValueNode( + DISCRIMINATOR.CREATE_ATA_IDEMPOTENT, + ), + ), + ), + ], + docs: [ + 'Create associated CToken account (idempotent - no-op if exists)', + ], + accounts: [ + instructionAccountNode({ + name: 'owner', + isSigner: false, + isWritable: false, + }), + instructionAccountNode({ + name: 'mint', + isSigner: false, + isWritable: false, + }), + instructionAccountNode({ + name: 'payer', + isSigner: true, + isWritable: true, + }), + instructionAccountNode({ + name: 'associatedTokenAccount', + isSigner: false, + isWritable: true, + defaultValue: pdaValueNode( + pdaLinkNode('associatedTokenAccount'), + ), + }), + instructionAccountNode({ + name: 'systemProgram', + isSigner: false, + isWritable: false, + defaultValue: publicKeyValueNode(SYSTEM_PROGRAM), + }), + instructionAccountNode({ + name: 'compressibleConfig', + isSigner: false, + isWritable: false, + }), + instructionAccountNode({ + name: 'rentSponsor', + isSigner: false, + isWritable: true, + }), + ], + arguments: [ + instructionArgumentNode({ + name: 'discriminator', + type: numberTypeNode('u8'), + defaultValue: numberValueNode( + DISCRIMINATOR.CREATE_ATA_IDEMPOTENT, + ), + defaultValueStrategy: 'omitted', + }), + instructionArgumentNode({ + name: 'bump', + type: numberTypeNode('u8'), + }), + instructionArgumentNode({ + name: 'compressibleConfig', + type: optionTypeNode(compressibleExtensionDataType), + }), + ], + }), + + // ---------------------------------------------------------------------- + // Transfer2 (discriminator: 101) - Batch transfer instruction + // ---------------------------------------------------------------------- + instructionNode({ + name: 'transfer2', + discriminators: [ + constantDiscriminatorNode( + constantValueNode( + numberTypeNode('u8'), + numberValueNode(DISCRIMINATOR.TRANSFER2), + ), + ), + ], + docs: [ + 'Batch transfer instruction for compressed/decompressed operations.', + 'Supports: transfer, compress, decompress, compress-and-close.', + ], + accounts: [ + instructionAccountNode({ + name: 'feePayer', + isSigner: true, + isWritable: true, + }), + instructionAccountNode({ + name: 'authority', + isSigner: true, + isWritable: false, + }), + instructionAccountNode({ + name: 'lightSystemProgram', + isSigner: false, + isWritable: false, + }), + instructionAccountNode({ + name: 'registeredProgramPda', + isSigner: false, + isWritable: false, + }), + instructionAccountNode({ + name: 'accountCompressionAuthority', + isSigner: false, + isWritable: false, + }), + instructionAccountNode({ + name: 'accountCompressionProgram', + isSigner: false, + isWritable: false, + }), + instructionAccountNode({ + name: 'selfProgram', + isSigner: false, + isWritable: false, + defaultValue: publicKeyValueNode( + LIGHT_TOKEN_PROGRAM_ID, + ), + }), + instructionAccountNode({ + name: 'systemProgram', + isSigner: false, + isWritable: false, + defaultValue: publicKeyValueNode(SYSTEM_PROGRAM), + }), + // Remaining accounts are dynamic based on the transfer + ], + arguments: [ + instructionArgumentNode({ + name: 'discriminator', + type: numberTypeNode('u8'), + defaultValue: numberValueNode(DISCRIMINATOR.TRANSFER2), + defaultValueStrategy: 'omitted', + }), + instructionArgumentNode({ + name: 'withTransactionHash', + type: booleanTypeNode(), + }), + instructionArgumentNode({ + name: 'withLamportsChangeAccountMerkleTreeIndex', + type: booleanTypeNode(), + }), + instructionArgumentNode({ + name: 'lamportsChangeAccountMerkleTreeIndex', + type: numberTypeNode('u8'), + }), + instructionArgumentNode({ + name: 'lamportsChangeAccountOwnerIndex', + type: numberTypeNode('u8'), + }), + instructionArgumentNode({ + name: 'outputQueue', + type: numberTypeNode('u8'), + }), + instructionArgumentNode({ + name: 'maxTopUp', + type: numberTypeNode('u16'), + }), + instructionArgumentNode({ + name: 'cpiContext', + type: optionTypeNode(cpiContextType), + }), + instructionArgumentNode({ + name: 'compressions', + type: optionTypeNode( + arrayTypeNode(compressionStructType), + ), + }), + // Note: proof, inTokenData, outTokenData, inLamports, outLamports, inTlv, outTlv + // are complex nested structures that will be handled by manual codecs + ], + }), + + // ---------------------------------------------------------------------- + // MintAction (discriminator: 103) - Batch mint operations + // ---------------------------------------------------------------------- + instructionNode({ + name: 'mintAction', + discriminators: [ + constantDiscriminatorNode( + constantValueNode( + numberTypeNode('u8'), + numberValueNode(DISCRIMINATOR.MINT_ACTION), + ), + ), + ], + docs: [ + 'Batch instruction for compressed mint management.', + 'Supports: CreateMint, MintTo, UpdateAuthorities, DecompressMint, etc.', + ], + accounts: [ + instructionAccountNode({ + name: 'feePayer', + isSigner: true, + isWritable: true, + }), + instructionAccountNode({ + name: 'authority', + isSigner: true, + isWritable: false, + }), + instructionAccountNode({ + name: 'lightSystemProgram', + isSigner: false, + isWritable: false, + }), + instructionAccountNode({ + name: 'registeredProgramPda', + isSigner: false, + isWritable: false, + }), + instructionAccountNode({ + name: 'accountCompressionAuthority', + isSigner: false, + isWritable: false, + }), + instructionAccountNode({ + name: 'accountCompressionProgram', + isSigner: false, + isWritable: false, + }), + instructionAccountNode({ + name: 'selfProgram', + isSigner: false, + isWritable: false, + defaultValue: publicKeyValueNode( + LIGHT_TOKEN_PROGRAM_ID, + ), + }), + instructionAccountNode({ + name: 'systemProgram', + isSigner: false, + isWritable: false, + defaultValue: publicKeyValueNode(SYSTEM_PROGRAM), + }), + // Remaining accounts are dynamic based on the mint action + ], + arguments: [ + instructionArgumentNode({ + name: 'discriminator', + type: numberTypeNode('u8'), + defaultValue: numberValueNode( + DISCRIMINATOR.MINT_ACTION, + ), + defaultValueStrategy: 'omitted', + }), + // MintAction has complex nested data handled by manual codecs + ], + }), + ], + + // ======================================================================== + // DEFINED TYPES + // ======================================================================== + definedTypes: [], + + // ======================================================================== + // ERRORS + // ======================================================================== + errors: [], + }), +); + +export default lightTokenIdl; diff --git a/js/token-idl/tsconfig.json b/js/token-idl/tsconfig.json new file mode 100644 index 0000000000..6f5308104c --- /dev/null +++ b/js/token-idl/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "declaration": true, + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*", "scripts/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/js/token-sdk/.prettierignore b/js/token-sdk/.prettierignore new file mode 100644 index 0000000000..b709f5b6b2 --- /dev/null +++ b/js/token-sdk/.prettierignore @@ -0,0 +1,6 @@ +node_modules +dist +build +coverage +*.d.ts.map +*.js.map diff --git a/js/token-sdk/.prettierrc b/js/token-sdk/.prettierrc new file mode 100644 index 0000000000..59be93e26f --- /dev/null +++ b/js/token-sdk/.prettierrc @@ -0,0 +1,10 @@ +{ + "semi": true, + "trailingComma": "all", + "singleQuote": true, + "printWidth": 80, + "useTabs": false, + "tabWidth": 4, + "bracketSpacing": true, + "arrowParens": "avoid" +} diff --git a/js/token-sdk/eslint.config.cjs b/js/token-sdk/eslint.config.cjs new file mode 100644 index 0000000000..06f1a6f6b0 --- /dev/null +++ b/js/token-sdk/eslint.config.cjs @@ -0,0 +1,113 @@ +const js = require('@eslint/js'); +const tseslint = require('@typescript-eslint/eslint-plugin'); +const tsParser = require('@typescript-eslint/parser'); + +module.exports = [ + { + ignores: [ + 'node_modules/**', + 'dist/**', + 'build/**', + 'coverage/**', + '*.config.js', + 'eslint.config.js', + 'eslint.config.cjs', + ], + }, + js.configs.recommended, + { + files: ['**/*.js', '**/*.cjs', '**/*.mjs'], + languageOptions: { + ecmaVersion: 2022, + sourceType: 'module', + globals: { + require: 'readonly', + module: 'readonly', + process: 'readonly', + __dirname: 'readonly', + __filename: 'readonly', + exports: 'readonly', + console: 'readonly', + Buffer: 'readonly', + }, + }, + }, + { + files: ['src/**/*.ts', 'src/**/*.tsx'], + languageOptions: { + parser: tsParser, + parserOptions: { + project: './tsconfig.json', + ecmaVersion: 2022, + sourceType: 'module', + }, + globals: { + process: 'readonly', + console: 'readonly', + __dirname: 'readonly', + __filename: 'readonly', + Buffer: 'readonly', + }, + }, + plugins: { + '@typescript-eslint': tseslint, + }, + rules: { + ...tseslint.configs.recommended.rules, + '@typescript-eslint/ban-ts-comment': 0, + '@typescript-eslint/no-explicit-any': 0, + '@typescript-eslint/no-var-requires': 0, + '@typescript-eslint/no-unused-vars': 0, + '@typescript-eslint/no-require-imports': 0, + 'no-prototype-builtins': 0, + 'no-undef': 0, + 'no-unused-vars': 0, + 'no-redeclare': 0, + }, + }, + { + files: [ + 'tests/**/*.ts', + '**/*.test.ts', + '**/*.spec.ts', + 'vitest.config.ts', + ], + languageOptions: { + parser: tsParser, + parserOptions: { + ecmaVersion: 2022, + sourceType: 'module', + }, + globals: { + process: 'readonly', + console: 'readonly', + __dirname: 'readonly', + __filename: 'readonly', + Buffer: 'readonly', + describe: 'readonly', + it: 'readonly', + expect: 'readonly', + beforeEach: 'readonly', + afterEach: 'readonly', + beforeAll: 'readonly', + afterAll: 'readonly', + test: 'readonly', + }, + }, + plugins: { + '@typescript-eslint': tseslint, + }, + rules: { + ...tseslint.configs.recommended.rules, + '@typescript-eslint/ban-ts-comment': 0, + '@typescript-eslint/no-explicit-any': 0, + '@typescript-eslint/no-var-requires': 0, + '@typescript-eslint/no-unused-vars': 0, + '@typescript-eslint/no-require-imports': 0, + 'no-prototype-builtins': 0, + 'no-undef': 0, + 'no-unused-vars': 0, + 'no-redeclare': 0, + }, + }, +]; diff --git a/js/token-sdk/package.json b/js/token-sdk/package.json new file mode 100644 index 0000000000..8d34848fe1 --- /dev/null +++ b/js/token-sdk/package.json @@ -0,0 +1,73 @@ +{ + "name": "@lightprotocol/token-sdk", + "version": "0.1.0", + "description": "Light Protocol Token SDK for Solana Kit (web3.js v2)", + "type": "module", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + }, + "./codecs": { + "import": "./dist/codecs/index.js", + "types": "./dist/codecs/index.d.ts" + }, + "./actions": { + "import": "./dist/actions/index.js", + "types": "./dist/actions/index.d.ts" + } + }, + "files": [ + "dist", + "src" + ], + "scripts": { + "build": "tsc", + "clean": "rm -rf dist", + "test": "vitest run", + "test:watch": "vitest", + "lint": "eslint .", + "format": "prettier --write .", + "prepublishOnly": "pnpm run build" + }, + "peerDependencies": { + "@solana/kit": "^2.1.0" + }, + "dependencies": { + "@solana/addresses": "^2.1.0", + "@solana/codecs": "^2.1.0", + "@solana/instructions": "^2.1.0", + "@solana/signers": "^2.1.0", + "@solana/transaction-messages": "^2.1.0", + "@solana/keys": "^2.1.0" + }, + "devDependencies": { + "@solana/kit": "^2.1.0", + "@eslint/js": "9.36.0", + "@typescript-eslint/eslint-plugin": "^8.44.0", + "@typescript-eslint/parser": "^8.44.0", + "eslint": "^9.36.0", + "prettier": "^3.3.3", + "typescript": "^5.7.3", + "vitest": "^2.1.8" + }, + "engines": { + "node": ">=18" + }, + "keywords": [ + "solana", + "light-protocol", + "compressed-token", + "zk-compression", + "web3" + ], + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/Lightprotocol/light-protocol.git", + "directory": "js/token-sdk" + } +} diff --git a/js/token-sdk/src/actions/account/close.ts b/js/token-sdk/src/actions/account/close.ts new file mode 100644 index 0000000000..8c899b3c8c --- /dev/null +++ b/js/token-sdk/src/actions/account/close.ts @@ -0,0 +1,55 @@ +/** + * Close token account action. + */ + +import type { Address } from '@solana/addresses'; +import type { IInstruction, IAccountMeta } from '@solana/instructions'; + +import { DISCRIMINATOR, LIGHT_TOKEN_PROGRAM_ID } from '../../constants.js'; + +// ============================================================================ +// CLOSE ACCOUNT INSTRUCTION +// ============================================================================ + +/** + * Parameters for closing a token account. + */ +export interface CloseAccountParams { + /** Token account to close */ + tokenAccount: Address; + /** Destination for remaining lamports */ + destination: Address; + /** Owner of the token account - must be signer */ + owner: Address; +} + +/** + * Creates a close token account instruction (discriminator: 9). + * + * Closes a decompressed CToken account and returns rent to the destination. + * For compressible accounts, rent goes to the rent sponsor. + * + * @param params - Close account parameters + * @returns The close instruction + */ +export function createCloseAccountInstruction( + params: CloseAccountParams, +): IInstruction { + const { tokenAccount, destination, owner } = params; + + // Build accounts + const accounts: IAccountMeta[] = [ + { address: tokenAccount, role: 1 }, // writable + { address: destination, role: 1 }, // writable + { address: owner, role: 2 }, // readonly+signer + ]; + + // Build instruction data (just discriminator) + const data = new Uint8Array([DISCRIMINATOR.CLOSE]); + + return { + programAddress: LIGHT_TOKEN_PROGRAM_ID, + accounts, + data, + }; +} diff --git a/js/token-sdk/src/actions/account/create-ata.ts b/js/token-sdk/src/actions/account/create-ata.ts new file mode 100644 index 0000000000..616efdb804 --- /dev/null +++ b/js/token-sdk/src/actions/account/create-ata.ts @@ -0,0 +1,119 @@ +/** + * Create Associated Token Account actions. + */ + +import type { Address } from '@solana/addresses'; +import type { IInstruction, IAccountMeta } from '@solana/instructions'; + +import { LIGHT_TOKEN_PROGRAM_ID, SYSTEM_PROGRAM_ID } from '../../constants.js'; +import { deriveAssociatedTokenAddress } from '../../utils/derivation.js'; +import { + encodeCreateAtaInstructionData, + defaultCompressibleParams, +} from '../../codecs/compressible.js'; +import type { CompressibleExtensionInstructionData } from '../../codecs/types.js'; + +// ============================================================================ +// CREATE ATA INSTRUCTION +// ============================================================================ + +/** + * Parameters for creating an associated token account. + */ +export interface CreateAtaParams { + /** Payer for the account creation */ + payer: Address; + /** Owner of the token account */ + owner: Address; + /** Mint address */ + mint: Address; + /** Compressible config account (for rent-free accounts) */ + compressibleConfig: Address; + /** Rent sponsor (for rent-free accounts) */ + rentSponsor: Address; + /** Compressible extension params (optional, uses defaults) */ + compressibleParams?: CompressibleExtensionInstructionData; + /** Whether to use idempotent variant (no-op if exists) */ + idempotent?: boolean; +} + +/** + * Result of ATA creation. + */ +export interface CreateAtaResult { + /** The derived ATA address */ + address: Address; + /** The PDA bump */ + bump: number; + /** The instruction to create the ATA */ + instruction: IInstruction; +} + +/** + * Creates an associated token account instruction. + * + * @param params - ATA creation parameters + * @returns The ATA address, bump, and instruction + */ +export async function createAssociatedTokenAccountInstruction( + params: CreateAtaParams, +): Promise { + const { + payer, + owner, + mint, + compressibleConfig, + rentSponsor, + compressibleParams = defaultCompressibleParams(), + idempotent = false, + } = params; + + // Derive the ATA address + const { address: ata, bump } = await deriveAssociatedTokenAddress( + owner, + mint, + ); + + // Build accounts + const accounts: IAccountMeta[] = [ + { address: owner, role: 0 }, // readonly + { address: mint, role: 0 }, // readonly + { address: payer, role: 3 }, // writable+signer + { address: ata, role: 1 }, // writable + { address: SYSTEM_PROGRAM_ID, role: 0 }, // readonly + { address: compressibleConfig, role: 0 }, // readonly + { address: rentSponsor, role: 1 }, // writable + ]; + + // Build instruction data + const data = encodeCreateAtaInstructionData( + { + bump, + compressibleConfig: compressibleParams, + }, + idempotent, + ); + + const instruction: IInstruction = { + programAddress: LIGHT_TOKEN_PROGRAM_ID, + accounts, + data, + }; + + return { address: ata, bump, instruction }; +} + +/** + * Creates an idempotent ATA instruction (no-op if account exists). + * + * @param params - ATA creation parameters (idempotent flag ignored) + * @returns The ATA address, bump, and instruction + */ +export async function createAssociatedTokenAccountIdempotentInstruction( + params: Omit, +): Promise { + return createAssociatedTokenAccountInstruction({ + ...params, + idempotent: true, + }); +} diff --git a/js/token-sdk/src/actions/account/index.ts b/js/token-sdk/src/actions/account/index.ts new file mode 100644 index 0000000000..96538b11a1 --- /dev/null +++ b/js/token-sdk/src/actions/account/index.ts @@ -0,0 +1,15 @@ +/** + * Account management actions for Light Token SDK. + */ + +export { + createAssociatedTokenAccountInstruction, + createAssociatedTokenAccountIdempotentInstruction, + type CreateAtaParams, + type CreateAtaResult, +} from './create-ata.js'; + +export { + createCloseAccountInstruction, + type CloseAccountParams, +} from './close.js'; diff --git a/js/token-sdk/src/actions/index.ts b/js/token-sdk/src/actions/index.ts new file mode 100644 index 0000000000..9e5509394a --- /dev/null +++ b/js/token-sdk/src/actions/index.ts @@ -0,0 +1,52 @@ +/** + * Light Token SDK Actions + * + * High-level action functions for Light Token operations. + */ + +// Transfer actions +export { + createTransferInstruction, + createTransferCheckedInstruction, + createTransferInterfaceInstruction, + requiresCompression, + type TransferParams, + type TransferCheckedParams, + type TransferType, + type TransferInterfaceParams, + type TransferInterfaceResult, +} from './transfer/index.js'; + +// Account actions +export { + createAssociatedTokenAccountInstruction, + createAssociatedTokenAccountIdempotentInstruction, + createCloseAccountInstruction, + type CreateAtaParams, + type CreateAtaResult, + type CloseAccountParams, +} from './account/index.js'; + +// Token operations +export { + createApproveInstruction, + createRevokeInstruction, + createBurnInstruction, + createBurnCheckedInstruction, + createFreezeInstruction, + createThawInstruction, + type ApproveParams, + type RevokeParams, + type BurnParams, + type BurnCheckedParams, + type FreezeParams, + type ThawParams, +} from './token/index.js'; + +// Mint actions +export { + createMintToInstruction, + createMintToCheckedInstruction, + type MintToParams, + type MintToCheckedParams, +} from './mint/index.js'; diff --git a/js/token-sdk/src/actions/mint/index.ts b/js/token-sdk/src/actions/mint/index.ts new file mode 100644 index 0000000000..ab44142fca --- /dev/null +++ b/js/token-sdk/src/actions/mint/index.ts @@ -0,0 +1,10 @@ +/** + * Mint actions for Light Token SDK. + */ + +export { + createMintToInstruction, + createMintToCheckedInstruction, + type MintToParams, + type MintToCheckedParams, +} from './mint-to.js'; diff --git a/js/token-sdk/src/actions/mint/mint-to.ts b/js/token-sdk/src/actions/mint/mint-to.ts new file mode 100644 index 0000000000..55e332dbf7 --- /dev/null +++ b/js/token-sdk/src/actions/mint/mint-to.ts @@ -0,0 +1,104 @@ +/** + * Mint-to token actions. + */ + +import type { Address } from '@solana/addresses'; +import type { IInstruction, IAccountMeta } from '@solana/instructions'; +import { getU64Encoder } from '@solana/codecs'; + +import { DISCRIMINATOR, LIGHT_TOKEN_PROGRAM_ID } from '../../constants.js'; + +// ============================================================================ +// MINT TO INSTRUCTION +// ============================================================================ + +/** + * Parameters for minting tokens. + */ +export interface MintToParams { + /** Mint address */ + mint: Address; + /** Token account to mint to */ + tokenAccount: Address; + /** Mint authority - must be signer */ + mintAuthority: Address; + /** Amount to mint */ + amount: bigint; +} + +/** + * Creates a mint-to instruction (discriminator: 7). + * + * Mints tokens to a decompressed CToken account. + * + * @param params - Mint-to parameters + * @returns The mint-to instruction + */ +export function createMintToInstruction(params: MintToParams): IInstruction { + const { mint, tokenAccount, mintAuthority, amount } = params; + + // Build accounts + const accounts: IAccountMeta[] = [ + { address: mint, role: 1 }, // writable + { address: tokenAccount, role: 1 }, // writable + { address: mintAuthority, role: 2 }, // readonly+signer + ]; + + // Build instruction data + const amountBytes = getU64Encoder().encode(amount); + const data = new Uint8Array(1 + amountBytes.length); + data[0] = DISCRIMINATOR.MINT_TO; + data.set(new Uint8Array(amountBytes), 1); + + return { + programAddress: LIGHT_TOKEN_PROGRAM_ID, + accounts, + data, + }; +} + +// ============================================================================ +// MINT TO CHECKED INSTRUCTION +// ============================================================================ + +/** + * Parameters for mint-to checked. + */ +export interface MintToCheckedParams extends MintToParams { + /** Expected decimals */ + decimals: number; +} + +/** + * Creates a mint-to checked instruction (discriminator: 14). + * + * Mints tokens with decimals validation. + * + * @param params - Mint-to checked parameters + * @returns The mint-to checked instruction + */ +export function createMintToCheckedInstruction( + params: MintToCheckedParams, +): IInstruction { + const { mint, tokenAccount, mintAuthority, amount, decimals } = params; + + // Build accounts + const accounts: IAccountMeta[] = [ + { address: mint, role: 1 }, // writable + { address: tokenAccount, role: 1 }, // writable + { address: mintAuthority, role: 2 }, // readonly+signer + ]; + + // Build instruction data + const amountBytes = getU64Encoder().encode(amount); + const data = new Uint8Array(1 + amountBytes.length + 1); + data[0] = DISCRIMINATOR.MINT_TO_CHECKED; + data.set(new Uint8Array(amountBytes), 1); + data[1 + amountBytes.length] = decimals; + + return { + programAddress: LIGHT_TOKEN_PROGRAM_ID, + accounts, + data, + }; +} diff --git a/js/token-sdk/src/actions/token/approve.ts b/js/token-sdk/src/actions/token/approve.ts new file mode 100644 index 0000000000..f19d983c50 --- /dev/null +++ b/js/token-sdk/src/actions/token/approve.ts @@ -0,0 +1,99 @@ +/** + * Approve and revoke delegate actions. + */ + +import type { Address } from '@solana/addresses'; +import type { IInstruction, IAccountMeta } from '@solana/instructions'; +import { getU64Encoder } from '@solana/codecs'; + +import { DISCRIMINATOR, LIGHT_TOKEN_PROGRAM_ID } from '../../constants.js'; + +// ============================================================================ +// APPROVE INSTRUCTION +// ============================================================================ + +/** + * Parameters for approving a delegate. + */ +export interface ApproveParams { + /** Token account to approve delegate on */ + tokenAccount: Address; + /** Delegate to approve */ + delegate: Address; + /** Owner of the token account - must be signer */ + owner: Address; + /** Amount to delegate */ + amount: bigint; +} + +/** + * Creates an approve instruction (discriminator: 4). + * + * Approves a delegate to transfer up to the specified amount. + * + * @param params - Approve parameters + * @returns The approve instruction + */ +export function createApproveInstruction(params: ApproveParams): IInstruction { + const { tokenAccount, delegate, owner, amount } = params; + + // Build accounts + const accounts: IAccountMeta[] = [ + { address: tokenAccount, role: 1 }, // writable + { address: delegate, role: 0 }, // readonly + { address: owner, role: 2 }, // readonly+signer + ]; + + // Build instruction data + const amountBytes = getU64Encoder().encode(amount); + const data = new Uint8Array(1 + amountBytes.length); + data[0] = DISCRIMINATOR.APPROVE; + data.set(new Uint8Array(amountBytes), 1); + + return { + programAddress: LIGHT_TOKEN_PROGRAM_ID, + accounts, + data, + }; +} + +// ============================================================================ +// REVOKE INSTRUCTION +// ============================================================================ + +/** + * Parameters for revoking a delegate. + */ +export interface RevokeParams { + /** Token account to revoke delegate from */ + tokenAccount: Address; + /** Owner of the token account - must be signer */ + owner: Address; +} + +/** + * Creates a revoke instruction (discriminator: 5). + * + * Revokes the delegate authority from the token account. + * + * @param params - Revoke parameters + * @returns The revoke instruction + */ +export function createRevokeInstruction(params: RevokeParams): IInstruction { + const { tokenAccount, owner } = params; + + // Build accounts + const accounts: IAccountMeta[] = [ + { address: tokenAccount, role: 1 }, // writable + { address: owner, role: 2 }, // readonly+signer + ]; + + // Build instruction data (just discriminator) + const data = new Uint8Array([DISCRIMINATOR.REVOKE]); + + return { + programAddress: LIGHT_TOKEN_PROGRAM_ID, + accounts, + data, + }; +} diff --git a/js/token-sdk/src/actions/token/burn.ts b/js/token-sdk/src/actions/token/burn.ts new file mode 100644 index 0000000000..8717dd1d82 --- /dev/null +++ b/js/token-sdk/src/actions/token/burn.ts @@ -0,0 +1,104 @@ +/** + * Burn token actions. + */ + +import type { Address } from '@solana/addresses'; +import type { IInstruction, IAccountMeta } from '@solana/instructions'; +import { getU64Encoder } from '@solana/codecs'; + +import { DISCRIMINATOR, LIGHT_TOKEN_PROGRAM_ID } from '../../constants.js'; + +// ============================================================================ +// BURN INSTRUCTION +// ============================================================================ + +/** + * Parameters for burning tokens. + */ +export interface BurnParams { + /** Token account to burn from */ + tokenAccount: Address; + /** Mint address */ + mint: Address; + /** Authority (owner or delegate) - must be signer */ + authority: Address; + /** Amount to burn */ + amount: bigint; +} + +/** + * Creates a burn instruction (discriminator: 8). + * + * Burns tokens from the token account and updates mint supply. + * + * @param params - Burn parameters + * @returns The burn instruction + */ +export function createBurnInstruction(params: BurnParams): IInstruction { + const { tokenAccount, mint, authority, amount } = params; + + // Build accounts + const accounts: IAccountMeta[] = [ + { address: tokenAccount, role: 1 }, // writable + { address: mint, role: 1 }, // writable + { address: authority, role: 2 }, // readonly+signer + ]; + + // Build instruction data + const amountBytes = getU64Encoder().encode(amount); + const data = new Uint8Array(1 + amountBytes.length); + data[0] = DISCRIMINATOR.BURN; + data.set(new Uint8Array(amountBytes), 1); + + return { + programAddress: LIGHT_TOKEN_PROGRAM_ID, + accounts, + data, + }; +} + +// ============================================================================ +// BURN CHECKED INSTRUCTION +// ============================================================================ + +/** + * Parameters for burn checked. + */ +export interface BurnCheckedParams extends BurnParams { + /** Expected decimals */ + decimals: number; +} + +/** + * Creates a burn checked instruction (discriminator: 15). + * + * Burns tokens with decimals validation. + * + * @param params - Burn checked parameters + * @returns The burn checked instruction + */ +export function createBurnCheckedInstruction( + params: BurnCheckedParams, +): IInstruction { + const { tokenAccount, mint, authority, amount, decimals } = params; + + // Build accounts + const accounts: IAccountMeta[] = [ + { address: tokenAccount, role: 1 }, // writable + { address: mint, role: 1 }, // writable + { address: authority, role: 2 }, // readonly+signer + ]; + + // Build instruction data + const amountBytes = getU64Encoder().encode(amount); + const data = new Uint8Array(1 + amountBytes.length + 1); + data[0] = DISCRIMINATOR.BURN_CHECKED; + data.set(new Uint8Array(amountBytes), 1); + data[1 + amountBytes.length] = decimals; + + return { + programAddress: LIGHT_TOKEN_PROGRAM_ID, + accounts, + data, + }; +} diff --git a/js/token-sdk/src/actions/token/freeze-thaw.ts b/js/token-sdk/src/actions/token/freeze-thaw.ts new file mode 100644 index 0000000000..f0becb0ab5 --- /dev/null +++ b/js/token-sdk/src/actions/token/freeze-thaw.ts @@ -0,0 +1,96 @@ +/** + * Freeze and thaw token account actions. + */ + +import type { Address } from '@solana/addresses'; +import type { IInstruction, IAccountMeta } from '@solana/instructions'; + +import { DISCRIMINATOR, LIGHT_TOKEN_PROGRAM_ID } from '../../constants.js'; + +// ============================================================================ +// FREEZE INSTRUCTION +// ============================================================================ + +/** + * Parameters for freezing a token account. + */ +export interface FreezeParams { + /** Token account to freeze */ + tokenAccount: Address; + /** Mint address */ + mint: Address; + /** Freeze authority - must be signer */ + freezeAuthority: Address; +} + +/** + * Creates a freeze instruction (discriminator: 10). + * + * Freezes a token account, preventing transfers. + * + * @param params - Freeze parameters + * @returns The freeze instruction + */ +export function createFreezeInstruction(params: FreezeParams): IInstruction { + const { tokenAccount, mint, freezeAuthority } = params; + + // Build accounts + const accounts: IAccountMeta[] = [ + { address: tokenAccount, role: 1 }, // writable + { address: mint, role: 0 }, // readonly + { address: freezeAuthority, role: 2 }, // readonly+signer + ]; + + // Build instruction data (just discriminator) + const data = new Uint8Array([DISCRIMINATOR.FREEZE]); + + return { + programAddress: LIGHT_TOKEN_PROGRAM_ID, + accounts, + data, + }; +} + +// ============================================================================ +// THAW INSTRUCTION +// ============================================================================ + +/** + * Parameters for thawing a token account. + */ +export interface ThawParams { + /** Token account to thaw */ + tokenAccount: Address; + /** Mint address */ + mint: Address; + /** Freeze authority - must be signer */ + freezeAuthority: Address; +} + +/** + * Creates a thaw instruction (discriminator: 11). + * + * Thaws a frozen token account, allowing transfers again. + * + * @param params - Thaw parameters + * @returns The thaw instruction + */ +export function createThawInstruction(params: ThawParams): IInstruction { + const { tokenAccount, mint, freezeAuthority } = params; + + // Build accounts + const accounts: IAccountMeta[] = [ + { address: tokenAccount, role: 1 }, // writable + { address: mint, role: 0 }, // readonly + { address: freezeAuthority, role: 2 }, // readonly+signer + ]; + + // Build instruction data (just discriminator) + const data = new Uint8Array([DISCRIMINATOR.THAW]); + + return { + programAddress: LIGHT_TOKEN_PROGRAM_ID, + accounts, + data, + }; +} diff --git a/js/token-sdk/src/actions/token/index.ts b/js/token-sdk/src/actions/token/index.ts new file mode 100644 index 0000000000..5a7c8ec231 --- /dev/null +++ b/js/token-sdk/src/actions/token/index.ts @@ -0,0 +1,24 @@ +/** + * Token operation actions for Light Token SDK. + */ + +export { + createApproveInstruction, + createRevokeInstruction, + type ApproveParams, + type RevokeParams, +} from './approve.js'; + +export { + createBurnInstruction, + createBurnCheckedInstruction, + type BurnParams, + type BurnCheckedParams, +} from './burn.js'; + +export { + createFreezeInstruction, + createThawInstruction, + type FreezeParams, + type ThawParams, +} from './freeze-thaw.js'; diff --git a/js/token-sdk/src/actions/transfer/index.ts b/js/token-sdk/src/actions/transfer/index.ts new file mode 100644 index 0000000000..830ed56376 --- /dev/null +++ b/js/token-sdk/src/actions/transfer/index.ts @@ -0,0 +1,18 @@ +/** + * Transfer actions for Light Token SDK. + */ + +export { + createTransferInstruction, + createTransferCheckedInstruction, + type TransferParams, + type TransferCheckedParams, +} from './transfer.js'; + +export { + createTransferInterfaceInstruction, + requiresCompression, + type TransferType, + type TransferInterfaceParams, + type TransferInterfaceResult, +} from './transfer-interface.js'; diff --git a/js/token-sdk/src/actions/transfer/transfer-interface.ts b/js/token-sdk/src/actions/transfer/transfer-interface.ts new file mode 100644 index 0000000000..1f97cab7b0 --- /dev/null +++ b/js/token-sdk/src/actions/transfer/transfer-interface.ts @@ -0,0 +1,127 @@ +/** + * Transfer interface - auto-routing between light-to-light, light-to-SPL, and SPL-to-light. + */ + +import type { Address } from '@solana/addresses'; +import type { IInstruction } from '@solana/instructions'; + +import { determineTransferType } from '../../utils/validation.js'; +import { createTransferInstruction } from './transfer.js'; + +// ============================================================================ +// TRANSFER INTERFACE +// ============================================================================ + +/** + * Transfer type for routing. + */ +export type TransferType = + | 'light-to-light' + | 'light-to-spl' + | 'spl-to-light' + | 'spl-to-spl'; + +/** + * Parameters for transfer interface. + */ +export interface TransferInterfaceParams { + /** Source account owner (to determine if Light or SPL) */ + sourceOwner: Address; + /** Destination account owner (to determine if Light or SPL) */ + destOwner: Address; + /** Source token account */ + source: Address; + /** Destination token account */ + destination: Address; + /** Amount to transfer */ + amount: bigint; + /** Authority for the transfer */ + authority: Address; + /** Mint address (for routing and pools) */ + mint: Address; + /** Maximum top-up for rent (optional) */ + maxTopUp?: number; +} + +/** + * Result of transfer interface routing. + */ +export interface TransferInterfaceResult { + /** The determined transfer type */ + transferType: TransferType; + /** The instruction(s) to execute */ + instructions: IInstruction[]; +} + +/** + * Creates transfer instruction(s) with automatic routing. + * + * Routes transfers based on account ownership: + * - Light-to-Light: Direct CToken transfer + * - Light-to-SPL: Decompress to SPL (requires Transfer2) + * - SPL-to-Light: Compress from SPL (requires Transfer2) + * - SPL-to-SPL: Falls through to SPL Token program + * + * @param params - Transfer interface parameters + * @returns The transfer type and instruction(s) + */ +export function createTransferInterfaceInstruction( + params: TransferInterfaceParams, +): TransferInterfaceResult { + const transferType = determineTransferType( + params.sourceOwner, + params.destOwner, + ); + + switch (transferType) { + case 'light-to-light': + return { + transferType, + instructions: [ + createTransferInstruction({ + source: params.source, + destination: params.destination, + amount: params.amount, + authority: params.authority, + maxTopUp: params.maxTopUp, + }), + ], + }; + + case 'light-to-spl': + // Light -> SPL requires Transfer2 with decompress mode + // This is a placeholder - full implementation requires validity proofs + throw new Error( + 'Light-to-SPL transfer requires Transfer2 with decompress mode. ' + + 'Use createDecompressInstruction() with a validity proof.', + ); + + case 'spl-to-light': + // SPL -> Light requires Transfer2 with compress mode + // This is a placeholder - full implementation requires validity proofs + throw new Error( + 'SPL-to-Light transfer requires Transfer2 with compress mode. ' + + 'Use createCompressInstruction() with a validity proof.', + ); + + case 'spl-to-spl': + throw new Error( + 'SPL-to-SPL transfers should use the SPL Token program directly.', + ); + } +} + +/** + * Helper to determine if a transfer requires compression operations. + * + * @param sourceOwner - Source account owner + * @param destOwner - Destination account owner + * @returns True if the transfer crosses the Light/SPL boundary + */ +export function requiresCompression( + sourceOwner: Address, + destOwner: Address, +): boolean { + const transferType = determineTransferType(sourceOwner, destOwner); + return transferType === 'light-to-spl' || transferType === 'spl-to-light'; +} diff --git a/js/token-sdk/src/actions/transfer/transfer.ts b/js/token-sdk/src/actions/transfer/transfer.ts new file mode 100644 index 0000000000..39a9474865 --- /dev/null +++ b/js/token-sdk/src/actions/transfer/transfer.ts @@ -0,0 +1,147 @@ +/** + * CToken transfer actions. + */ + +import type { Address } from '@solana/addresses'; +import type { IInstruction, IAccountMeta } from '@solana/instructions'; +import { getU64Encoder, getU16Encoder } from '@solana/codecs'; + +import { + DISCRIMINATOR, + LIGHT_TOKEN_PROGRAM_ID, + SYSTEM_PROGRAM_ID, +} from '../../constants.js'; + +// ============================================================================ +// TRANSFER INSTRUCTION +// ============================================================================ + +/** + * Parameters for CToken transfer. + */ +export interface TransferParams { + /** Source CToken account */ + source: Address; + /** Destination CToken account */ + destination: Address; + /** Amount to transfer */ + amount: bigint; + /** Authority (owner or delegate) - must be signer */ + authority: Address; + /** Maximum lamports for rent top-up (optional, 0 = no limit) */ + maxTopUp?: number; + /** Fee payer for rent top-ups (optional, defaults to authority) */ + feePayer?: Address; +} + +/** + * Creates a CToken transfer instruction (discriminator: 3). + * + * Transfers tokens between decompressed CToken accounts. + * + * @param params - Transfer parameters + * @returns The transfer instruction + */ +export function createTransferInstruction( + params: TransferParams, +): IInstruction { + const { source, destination, amount, authority, maxTopUp, feePayer } = + params; + + // Build accounts + const accounts: IAccountMeta[] = [ + { address: source, role: 1 }, // writable + { address: destination, role: 1 }, // writable + { + address: authority, + role: maxTopUp !== undefined && !feePayer ? 3 : 2, // writable+signer if paying, else readonly+signer + }, + { address: SYSTEM_PROGRAM_ID, role: 0 }, // readonly + ]; + + // Add fee payer if provided + if (feePayer) { + accounts.push({ address: feePayer, role: 3 }); // writable+signer + } + + // Build instruction data + const amountBytes = getU64Encoder().encode(amount); + const maxTopUpBytes = + maxTopUp !== undefined + ? getU16Encoder().encode(maxTopUp) + : new Uint8Array(0); + + const data = new Uint8Array(1 + amountBytes.length + maxTopUpBytes.length); + data[0] = DISCRIMINATOR.TRANSFER; + data.set(new Uint8Array(amountBytes), 1); + if (maxTopUpBytes.length > 0) { + data.set(new Uint8Array(maxTopUpBytes), 1 + amountBytes.length); + } + + return { + programAddress: LIGHT_TOKEN_PROGRAM_ID, + accounts, + data, + }; +} + +// ============================================================================ +// TRANSFER CHECKED INSTRUCTION +// ============================================================================ + +/** + * Parameters for CToken transfer checked. + */ +export interface TransferCheckedParams extends TransferParams { + /** Mint address for validation */ + mint: Address; + /** Expected decimals */ + decimals: number; +} + +/** + * Creates a CToken transfer checked instruction (discriminator: 12). + * + * Transfers tokens with decimals validation. + * + * @param params - Transfer checked parameters + * @returns The transfer checked instruction + */ +export function createTransferCheckedInstruction( + params: TransferCheckedParams, +): IInstruction { + const { source, mint, destination, amount, authority, decimals, maxTopUp } = + params; + + // Build accounts + const accounts: IAccountMeta[] = [ + { address: source, role: 1 }, // writable + { address: mint, role: 0 }, // readonly + { address: destination, role: 1 }, // writable + { address: authority, role: 2 }, // readonly+signer + { address: SYSTEM_PROGRAM_ID, role: 0 }, // readonly + ]; + + // Build instruction data + const amountBytes = getU64Encoder().encode(amount); + const maxTopUpBytes = + maxTopUp !== undefined + ? getU16Encoder().encode(maxTopUp) + : new Uint8Array(0); + + const data = new Uint8Array( + 1 + amountBytes.length + 1 + maxTopUpBytes.length, + ); + data[0] = DISCRIMINATOR.TRANSFER_CHECKED; + data.set(new Uint8Array(amountBytes), 1); + data[1 + amountBytes.length] = decimals; + if (maxTopUpBytes.length > 0) { + data.set(new Uint8Array(maxTopUpBytes), 1 + amountBytes.length + 1); + } + + return { + programAddress: LIGHT_TOKEN_PROGRAM_ID, + accounts, + data, + }; +} diff --git a/js/token-sdk/src/codecs/compressible.ts b/js/token-sdk/src/codecs/compressible.ts new file mode 100644 index 0000000000..8019cdfcbd --- /dev/null +++ b/js/token-sdk/src/codecs/compressible.ts @@ -0,0 +1,172 @@ +/** + * Compressible extension codecs using Solana Kit patterns. + */ + +import { + type Codec, + type Decoder, + type Encoder, + combineCodec, + getStructDecoder, + getStructEncoder, + getU8Decoder, + getU8Encoder, + getU16Decoder, + getU16Encoder, + getU32Decoder, + getU32Encoder, + getBytesDecoder, + getBytesEncoder, + getArrayDecoder, + getArrayEncoder, + addDecoderSizePrefix, + addEncoderSizePrefix, + getOptionEncoder, + getOptionDecoder, + fixEncoderSize, + fixDecoderSize, +} from '@solana/codecs'; + +import type { + CompressToPubkey, + CompressibleExtensionInstructionData, + CreateAtaInstructionData, +} from './types.js'; + +import { DISCRIMINATOR } from '../constants.js'; + +// ============================================================================ +// VEC CODEC (Borsh-style: u32 length prefix) +// ============================================================================ + +function getVecEncoder(itemEncoder: Encoder): Encoder { + return addEncoderSizePrefix( + getArrayEncoder(itemEncoder), + getU32Encoder(), + ) as Encoder; +} + +function getVecDecoder(itemDecoder: Decoder): Decoder { + return addDecoderSizePrefix(getArrayDecoder(itemDecoder), getU32Decoder()); +} + +// ============================================================================ +// COMPRESS TO PUBKEY CODEC +// ============================================================================ + +export const getCompressToPubkeyEncoder = (): Encoder => + getStructEncoder([ + ['bump', getU8Encoder()], + ['programId', fixEncoderSize(getBytesEncoder(), 32)], + ['seeds', getVecEncoder(getVecEncoder(getU8Encoder()))], + ]) as unknown as Encoder; + +export const getCompressToPubkeyDecoder = (): Decoder => + getStructDecoder([ + ['bump', getU8Decoder()], + ['programId', fixDecoderSize(getBytesDecoder(), 32)], + ['seeds', getVecDecoder(getVecDecoder(getU8Decoder()))], + ]) as unknown as Decoder; + +export const getCompressToPubkeyCodec = (): Codec => + combineCodec(getCompressToPubkeyEncoder(), getCompressToPubkeyDecoder()); + +// ============================================================================ +// COMPRESSIBLE EXTENSION INSTRUCTION DATA CODEC +// ============================================================================ + +export const getCompressibleExtensionDataEncoder = + (): Encoder => + getStructEncoder([ + ['tokenAccountVersion', getU8Encoder()], + ['rentPayment', getU16Encoder()], + ['compressionOnly', getU8Encoder()], + ['writeTopUp', getU32Encoder()], + [ + 'compressToPubkey', + getOptionEncoder(getCompressToPubkeyEncoder()), + ], + ]); + +export const getCompressibleExtensionDataDecoder = + (): Decoder => + getStructDecoder([ + ['tokenAccountVersion', getU8Decoder()], + ['rentPayment', getU16Decoder()], + ['compressionOnly', getU8Decoder()], + ['writeTopUp', getU32Decoder()], + [ + 'compressToPubkey', + getOptionDecoder(getCompressToPubkeyDecoder()), + ], + ]) as unknown as Decoder; + +export const getCompressibleExtensionDataCodec = + (): Codec => + combineCodec( + getCompressibleExtensionDataEncoder(), + getCompressibleExtensionDataDecoder(), + ); + +// ============================================================================ +// CREATE ATA INSTRUCTION DATA CODEC +// ============================================================================ + +export const getCreateAtaDataEncoder = (): Encoder => + getStructEncoder([ + ['bump', getU8Encoder()], + [ + 'compressibleConfig', + getOptionEncoder(getCompressibleExtensionDataEncoder()), + ], + ]); + +export const getCreateAtaDataDecoder = (): Decoder => + getStructDecoder([ + ['bump', getU8Decoder()], + [ + 'compressibleConfig', + getOptionDecoder(getCompressibleExtensionDataDecoder()), + ], + ]) as unknown as Decoder; + +export const getCreateAtaDataCodec = (): Codec => + combineCodec(getCreateAtaDataEncoder(), getCreateAtaDataDecoder()); + +// ============================================================================ +// FULL INSTRUCTION ENCODERS +// ============================================================================ + +/** + * Encodes the CreateAssociatedTokenAccount instruction data. + */ +export function encodeCreateAtaInstructionData( + data: CreateAtaInstructionData, + idempotent = false, +): Uint8Array { + const discriminator = idempotent + ? DISCRIMINATOR.CREATE_ATA_IDEMPOTENT + : DISCRIMINATOR.CREATE_ATA; + + const dataEncoder = getCreateAtaDataEncoder(); + const dataBytes = dataEncoder.encode(data); + + const result = new Uint8Array(1 + dataBytes.length); + result[0] = discriminator; + result.set(new Uint8Array(dataBytes), 1); + + return result; +} + +/** + * Default compressible extension params for rent-free ATAs. + */ +export function defaultCompressibleParams(): CompressibleExtensionInstructionData { + return { + tokenAccountVersion: 0, + rentPayment: 0, + compressionOnly: 0, + writeTopUp: 0, + compressToPubkey: null, + }; +} diff --git a/js/token-sdk/src/codecs/index.ts b/js/token-sdk/src/codecs/index.ts new file mode 100644 index 0000000000..532c2139e8 --- /dev/null +++ b/js/token-sdk/src/codecs/index.ts @@ -0,0 +1,49 @@ +/** + * Light Token SDK Codecs + * + * Serialization codecs for Light Token instruction data using Solana Kit patterns. + */ + +// Types +export * from './types.js'; + +// Transfer2 codecs +export { + getCompressionEncoder, + getCompressionDecoder, + getCompressionCodec, + getPackedMerkleContextEncoder, + getPackedMerkleContextDecoder, + getPackedMerkleContextCodec, + getMultiInputTokenDataEncoder, + getMultiInputTokenDataDecoder, + getMultiInputTokenDataCodec, + getMultiTokenOutputDataEncoder, + getMultiTokenOutputDataDecoder, + getMultiTokenOutputDataCodec, + getCpiContextEncoder, + getCpiContextDecoder, + getCpiContextCodec, + getCompressedProofEncoder, + getCompressedProofDecoder, + getCompressedProofCodec, + getTransfer2BaseEncoder, + getTransfer2BaseDecoder, + encodeTransfer2InstructionData, + type Transfer2BaseInstructionData, +} from './transfer2.js'; + +// Compressible codecs +export { + getCompressToPubkeyEncoder, + getCompressToPubkeyDecoder, + getCompressToPubkeyCodec, + getCompressibleExtensionDataEncoder, + getCompressibleExtensionDataDecoder, + getCompressibleExtensionDataCodec, + getCreateAtaDataEncoder, + getCreateAtaDataDecoder, + getCreateAtaDataCodec, + encodeCreateAtaInstructionData, + defaultCompressibleParams, +} from './compressible.js'; diff --git a/js/token-sdk/src/codecs/transfer2.ts b/js/token-sdk/src/codecs/transfer2.ts new file mode 100644 index 0000000000..5eac0adaeb --- /dev/null +++ b/js/token-sdk/src/codecs/transfer2.ts @@ -0,0 +1,396 @@ +/** + * Transfer2 instruction codecs using Solana Kit patterns. + */ + +import { + type Codec, + type Decoder, + type Encoder, + combineCodec, + getStructDecoder, + getStructEncoder, + getU8Decoder, + getU8Encoder, + getU16Decoder, + getU16Encoder, + getU32Decoder, + getU32Encoder, + getU64Decoder, + getU64Encoder, + getBooleanDecoder, + getBooleanEncoder, + getArrayDecoder, + getArrayEncoder, + getBytesDecoder, + getBytesEncoder, + addDecoderSizePrefix, + addEncoderSizePrefix, + getOptionEncoder, + getOptionDecoder, + fixEncoderSize, + fixDecoderSize, +} from '@solana/codecs'; + +import type { + Compression, + PackedMerkleContext, + MultiInputTokenDataWithContext, + MultiTokenTransferOutputData, + CompressedCpiContext, + CompressedProof, + Transfer2InstructionData, +} from './types.js'; + +import { DISCRIMINATOR } from '../constants.js'; + +// ============================================================================ +// COMPRESSION CODEC +// ============================================================================ + +export const getCompressionEncoder = (): Encoder => + getStructEncoder([ + ['mode', getU8Encoder()], + ['amount', getU64Encoder()], + ['mint', getU8Encoder()], + ['sourceOrRecipient', getU8Encoder()], + ['authority', getU8Encoder()], + ['poolAccountIndex', getU8Encoder()], + ['poolIndex', getU8Encoder()], + ['bump', getU8Encoder()], + ['decimals', getU8Encoder()], + ]); + +export const getCompressionDecoder = (): Decoder => + getStructDecoder([ + ['mode', getU8Decoder()], + ['amount', getU64Decoder()], + ['mint', getU8Decoder()], + ['sourceOrRecipient', getU8Decoder()], + ['authority', getU8Decoder()], + ['poolAccountIndex', getU8Decoder()], + ['poolIndex', getU8Decoder()], + ['bump', getU8Decoder()], + ['decimals', getU8Decoder()], + ]); + +export const getCompressionCodec = (): Codec => + combineCodec(getCompressionEncoder(), getCompressionDecoder()); + +// ============================================================================ +// PACKED MERKLE CONTEXT CODEC +// ============================================================================ + +export const getPackedMerkleContextEncoder = (): Encoder => + getStructEncoder([ + ['merkleTreePubkeyIndex', getU8Encoder()], + ['queuePubkeyIndex', getU8Encoder()], + ['leafIndex', getU32Encoder()], + ['proveByIndex', getBooleanEncoder()], + ]); + +export const getPackedMerkleContextDecoder = (): Decoder => + getStructDecoder([ + ['merkleTreePubkeyIndex', getU8Decoder()], + ['queuePubkeyIndex', getU8Decoder()], + ['leafIndex', getU32Decoder()], + ['proveByIndex', getBooleanDecoder()], + ]); + +export const getPackedMerkleContextCodec = (): Codec => + combineCodec( + getPackedMerkleContextEncoder(), + getPackedMerkleContextDecoder(), + ); + +// ============================================================================ +// INPUT TOKEN DATA CODEC +// ============================================================================ + +export const getMultiInputTokenDataEncoder = + (): Encoder => + getStructEncoder([ + ['owner', getU8Encoder()], + ['amount', getU64Encoder()], + ['hasDelegate', getBooleanEncoder()], + ['delegate', getU8Encoder()], + ['mint', getU8Encoder()], + ['version', getU8Encoder()], + ['merkleContext', getPackedMerkleContextEncoder()], + ['rootIndex', getU16Encoder()], + ]); + +export const getMultiInputTokenDataDecoder = + (): Decoder => + getStructDecoder([ + ['owner', getU8Decoder()], + ['amount', getU64Decoder()], + ['hasDelegate', getBooleanDecoder()], + ['delegate', getU8Decoder()], + ['mint', getU8Decoder()], + ['version', getU8Decoder()], + ['merkleContext', getPackedMerkleContextDecoder()], + ['rootIndex', getU16Decoder()], + ]); + +export const getMultiInputTokenDataCodec = + (): Codec => + combineCodec( + getMultiInputTokenDataEncoder(), + getMultiInputTokenDataDecoder(), + ); + +// ============================================================================ +// OUTPUT TOKEN DATA CODEC +// ============================================================================ + +export const getMultiTokenOutputDataEncoder = + (): Encoder => + getStructEncoder([ + ['owner', getU8Encoder()], + ['amount', getU64Encoder()], + ['hasDelegate', getBooleanEncoder()], + ['delegate', getU8Encoder()], + ['mint', getU8Encoder()], + ['version', getU8Encoder()], + ]); + +export const getMultiTokenOutputDataDecoder = + (): Decoder => + getStructDecoder([ + ['owner', getU8Decoder()], + ['amount', getU64Decoder()], + ['hasDelegate', getBooleanDecoder()], + ['delegate', getU8Decoder()], + ['mint', getU8Decoder()], + ['version', getU8Decoder()], + ]); + +export const getMultiTokenOutputDataCodec = + (): Codec => + combineCodec( + getMultiTokenOutputDataEncoder(), + getMultiTokenOutputDataDecoder(), + ); + +// ============================================================================ +// CPI CONTEXT CODEC +// ============================================================================ + +export const getCpiContextEncoder = (): Encoder => + getStructEncoder([ + ['setContext', getBooleanEncoder()], + ['firstSetContext', getBooleanEncoder()], + ['cpiContextAccountIndex', getU8Encoder()], + ]); + +export const getCpiContextDecoder = (): Decoder => + getStructDecoder([ + ['setContext', getBooleanDecoder()], + ['firstSetContext', getBooleanDecoder()], + ['cpiContextAccountIndex', getU8Decoder()], + ]); + +export const getCpiContextCodec = (): Codec => + combineCodec(getCpiContextEncoder(), getCpiContextDecoder()); + +// ============================================================================ +// PROOF CODEC +// ============================================================================ + +export const getCompressedProofEncoder = (): Encoder => + getStructEncoder([ + ['a', fixEncoderSize(getBytesEncoder(), 32)], + ['b', fixEncoderSize(getBytesEncoder(), 64)], + ['c', fixEncoderSize(getBytesEncoder(), 32)], + ]) as unknown as Encoder; + +export const getCompressedProofDecoder = (): Decoder => + getStructDecoder([ + ['a', fixDecoderSize(getBytesDecoder(), 32)], + ['b', fixDecoderSize(getBytesDecoder(), 64)], + ['c', fixDecoderSize(getBytesDecoder(), 32)], + ]) as unknown as Decoder; + +export const getCompressedProofCodec = (): Codec => + combineCodec(getCompressedProofEncoder(), getCompressedProofDecoder()); + +// ============================================================================ +// VECTOR CODECS (with u32 length prefix for Borsh compatibility) +// ============================================================================ + +/** + * Creates an encoder for a Vec type (Borsh style: u32 length prefix). + */ +function getVecEncoder(itemEncoder: Encoder): Encoder { + return addEncoderSizePrefix( + getArrayEncoder(itemEncoder), + getU32Encoder(), + ) as Encoder; +} + +/** + * Creates a decoder for a Vec type (Borsh style: u32 length prefix). + */ +function getVecDecoder(itemDecoder: Decoder): Decoder { + return addDecoderSizePrefix(getArrayDecoder(itemDecoder), getU32Decoder()); +} + +// ============================================================================ +// TRANSFER2 INSTRUCTION DATA CODEC (Base fields only) +// Note: TLV fields require manual serialization due to complex nested structures +// ============================================================================ + +/** + * Base Transfer2 instruction data (without TLV fields). + */ +export interface Transfer2BaseInstructionData { + withTransactionHash: boolean; + withLamportsChangeAccountMerkleTreeIndex: boolean; + lamportsChangeAccountMerkleTreeIndex: number; + lamportsChangeAccountOwnerIndex: number; + outputQueue: number; + maxTopUp: number; + cpiContext: CompressedCpiContext | null; + compressions: readonly Compression[] | null; + proof: CompressedProof | null; + inTokenData: readonly MultiInputTokenDataWithContext[]; + outTokenData: readonly MultiTokenTransferOutputData[]; + inLamports: readonly bigint[] | null; + outLamports: readonly bigint[] | null; +} + +export const getTransfer2BaseEncoder = + (): Encoder => + getStructEncoder([ + ['withTransactionHash', getBooleanEncoder()], + ['withLamportsChangeAccountMerkleTreeIndex', getBooleanEncoder()], + ['lamportsChangeAccountMerkleTreeIndex', getU8Encoder()], + ['lamportsChangeAccountOwnerIndex', getU8Encoder()], + ['outputQueue', getU8Encoder()], + ['maxTopUp', getU16Encoder()], + ['cpiContext', getOptionEncoder(getCpiContextEncoder())], + [ + 'compressions', + getOptionEncoder(getVecEncoder(getCompressionEncoder())), + ], + ['proof', getOptionEncoder(getCompressedProofEncoder())], + ['inTokenData', getVecEncoder(getMultiInputTokenDataEncoder())], + ['outTokenData', getVecEncoder(getMultiTokenOutputDataEncoder())], + ['inLamports', getOptionEncoder(getVecEncoder(getU64Encoder()))], + ['outLamports', getOptionEncoder(getVecEncoder(getU64Encoder()))], + ]) as unknown as Encoder; + +export const getTransfer2BaseDecoder = + (): Decoder => + getStructDecoder([ + ['withTransactionHash', getBooleanDecoder()], + ['withLamportsChangeAccountMerkleTreeIndex', getBooleanDecoder()], + ['lamportsChangeAccountMerkleTreeIndex', getU8Decoder()], + ['lamportsChangeAccountOwnerIndex', getU8Decoder()], + ['outputQueue', getU8Decoder()], + ['maxTopUp', getU16Decoder()], + ['cpiContext', getOptionDecoder(getCpiContextDecoder())], + [ + 'compressions', + getOptionDecoder(getVecDecoder(getCompressionDecoder())), + ], + ['proof', getOptionDecoder(getCompressedProofDecoder())], + ['inTokenData', getVecDecoder(getMultiInputTokenDataDecoder())], + ['outTokenData', getVecDecoder(getMultiTokenOutputDataDecoder())], + ['inLamports', getOptionDecoder(getVecDecoder(getU64Decoder()))], + ['outLamports', getOptionDecoder(getVecDecoder(getU64Decoder()))], + ]) as unknown as Decoder; + +// ============================================================================ +// TRANSFER2 FULL ENCODER (with discriminator and TLV fields) +// ============================================================================ + +/** + * Encodes the full Transfer2 instruction data including discriminator and TLV. + */ +export function encodeTransfer2InstructionData( + data: Transfer2InstructionData, +): Uint8Array { + const baseEncoder = getTransfer2BaseEncoder(); + + // Encode base data + const baseData: Transfer2BaseInstructionData = { + withTransactionHash: data.withTransactionHash, + withLamportsChangeAccountMerkleTreeIndex: + data.withLamportsChangeAccountMerkleTreeIndex, + lamportsChangeAccountMerkleTreeIndex: + data.lamportsChangeAccountMerkleTreeIndex, + lamportsChangeAccountOwnerIndex: data.lamportsChangeAccountOwnerIndex, + outputQueue: data.outputQueue, + maxTopUp: data.maxTopUp, + cpiContext: data.cpiContext, + compressions: data.compressions, + proof: data.proof, + inTokenData: data.inTokenData, + outTokenData: data.outTokenData, + inLamports: data.inLamports, + outLamports: data.outLamports, + }; + + const baseBytes = baseEncoder.encode(baseData); + + // Encode TLV fields (Option>>) + const inTlvBytes = encodeTlv(data.inTlv); + const outTlvBytes = encodeTlv(data.outTlv); + + // Combine: discriminator + base + inTlv + outTlv + const result = new Uint8Array( + 1 + baseBytes.length + inTlvBytes.length + outTlvBytes.length, + ); + result[0] = DISCRIMINATOR.TRANSFER2; + result.set(baseBytes, 1); + result.set(inTlvBytes, 1 + baseBytes.length); + result.set(outTlvBytes, 1 + baseBytes.length + inTlvBytes.length); + + return result; +} + +/** + * Encodes TLV data as Option>>. + * For now, we support null (None) or empty arrays. + * Full extension serialization would require additional codec implementations. + */ +function encodeTlv(tlv: unknown[][] | null): Uint8Array { + if (tlv === null) { + // Option::None + return new Uint8Array([0]); + } + + // Option::Some + Vec> + const chunks: Uint8Array[] = []; + + // Option::Some + chunks.push(new Uint8Array([1])); + + // Outer vec length (u32) + const outerLen = new Uint8Array(4); + new DataView(outerLen.buffer).setUint32(0, tlv.length, true); + chunks.push(outerLen); + + // For each inner vec + for (const innerVec of tlv) { + // Inner vec length (u32) + const innerLen = new Uint8Array(4); + new DataView(innerLen.buffer).setUint32(0, innerVec.length, true); + chunks.push(innerLen); + + // Note: Extension serialization would go here + // For now, we only support empty inner vectors + } + + // Concatenate all chunks + const totalLen = chunks.reduce((sum, chunk) => sum + chunk.length, 0); + const result = new Uint8Array(totalLen); + let offset = 0; + for (const chunk of chunks) { + result.set(chunk, offset); + offset += chunk.length; + } + + return result; +} diff --git a/js/token-sdk/src/codecs/types.ts b/js/token-sdk/src/codecs/types.ts new file mode 100644 index 0000000000..cdd3f959dc --- /dev/null +++ b/js/token-sdk/src/codecs/types.ts @@ -0,0 +1,304 @@ +/** + * Type definitions for Light Token codecs + */ + +import type { Address } from '@solana/addresses'; + +// ============================================================================ +// COMPRESSION TYPES +// ============================================================================ + +/** + * Compression operation for Transfer2 instruction. + * Describes how to compress/decompress tokens. + */ +export interface Compression { + /** Compression mode: 0=compress, 1=decompress, 2=compress_and_close */ + mode: number; + /** Amount to compress/decompress */ + amount: bigint; + /** Index of mint in packed accounts */ + mint: number; + /** Index of source (compress) or recipient (decompress) in packed accounts */ + sourceOrRecipient: number; + /** Index of authority in packed accounts */ + authority: number; + /** Index of pool account in packed accounts */ + poolAccountIndex: number; + /** Pool index (for multi-pool mints) */ + poolIndex: number; + /** PDA bump for pool derivation */ + bump: number; + /** Token decimals (or rent_sponsor_is_signer flag for CompressAndClose) */ + decimals: number; +} + +// ============================================================================ +// MERKLE CONTEXT TYPES +// ============================================================================ + +/** + * Packed merkle context for compressed accounts. + */ +export interface PackedMerkleContext { + /** Index of merkle tree pubkey in packed accounts */ + merkleTreePubkeyIndex: number; + /** Index of queue pubkey in packed accounts */ + queuePubkeyIndex: number; + /** Leaf index in the merkle tree */ + leafIndex: number; + /** Whether to prove by index (vs by hash) */ + proveByIndex: boolean; +} + +// ============================================================================ +// TOKEN DATA TYPES +// ============================================================================ + +/** + * Input token data with merkle context for Transfer2. + */ +export interface MultiInputTokenDataWithContext { + /** Index of owner in packed accounts */ + owner: number; + /** Token amount */ + amount: bigint; + /** Whether token has a delegate */ + hasDelegate: boolean; + /** Index of delegate in packed accounts (if hasDelegate) */ + delegate: number; + /** Index of mint in packed accounts */ + mint: number; + /** Token account version */ + version: number; + /** Merkle context for the compressed account */ + merkleContext: PackedMerkleContext; + /** Root index for validity proof */ + rootIndex: number; +} + +/** + * Output token data for Transfer2. + */ +export interface MultiTokenTransferOutputData { + /** Index of owner in packed accounts */ + owner: number; + /** Token amount */ + amount: bigint; + /** Whether token has a delegate */ + hasDelegate: boolean; + /** Index of delegate in packed accounts (if hasDelegate) */ + delegate: number; + /** Index of mint in packed accounts */ + mint: number; + /** Token account version */ + version: number; +} + +// ============================================================================ +// CPI CONTEXT +// ============================================================================ + +/** + * CPI context for compressed account operations. + */ +export interface CompressedCpiContext { + /** Whether to set the CPI context */ + setContext: boolean; + /** Whether this is the first set context call */ + firstSetContext: boolean; + /** Index of CPI context account in packed accounts */ + cpiContextAccountIndex: number; +} + +// ============================================================================ +// PROOF TYPES +// ============================================================================ + +/** + * Groth16 proof for compressed account validity. + */ +export interface CompressedProof { + /** Proof element A (32 bytes) */ + a: Uint8Array; + /** Proof element B (64 bytes) */ + b: Uint8Array; + /** Proof element C (32 bytes) */ + c: Uint8Array; +} + +// ============================================================================ +// EXTENSION TYPES +// ============================================================================ + +/** + * Token metadata extension data. + */ +export interface TokenMetadataExtension { + /** Update authority (optional) */ + updateAuthority: Address | null; + /** Token name */ + name: Uint8Array; + /** Token symbol */ + symbol: Uint8Array; + /** Token URI */ + uri: Uint8Array; + /** Additional metadata key-value pairs */ + additionalMetadata: Array<{ key: Uint8Array; value: Uint8Array }> | null; +} + +/** + * CompressedOnly extension data. + */ +export interface CompressedOnlyExtension { + /** Delegated amount */ + delegatedAmount: bigint; + /** Withheld transfer fee */ + withheldTransferFee: bigint; + /** Whether account is frozen */ + isFrozen: boolean; + /** Compression index */ + compressionIndex: number; + /** Whether this is an ATA */ + isAta: boolean; + /** PDA bump */ + bump: number; + /** Owner index in packed accounts */ + ownerIndex: number; +} + +/** + * Rent configuration for compressible accounts. + */ +export interface RentConfig { + /** Base rent in lamports */ + baseRent: number; + /** Compression cost in lamports */ + compressionCost: number; + /** Lamports per byte per epoch */ + lamportsPerBytePerEpoch: number; + /** Maximum funded epochs */ + maxFundedEpochs: number; + /** Maximum top-up amount */ + maxTopUp: number; +} + +/** + * Compression info for compressible accounts. + */ +export interface CompressionInfo { + /** Config account version */ + configAccountVersion: number; + /** Compress-to pubkey type: 0=none, 1=owner, 2=custom */ + compressToPubkey: number; + /** Account version */ + accountVersion: number; + /** Lamports per write operation */ + lamportsPerWrite: number; + /** Compression authority */ + compressionAuthority: Address; + /** Rent sponsor */ + rentSponsor: Address; + /** Last claimed slot */ + lastClaimedSlot: bigint; + /** Rent exemption paid */ + rentExemptionPaid: number; + /** Reserved bytes */ + reserved: number; + /** Rent configuration */ + rentConfig: RentConfig; +} + +/** + * Extension instruction data (union type). + */ +export type ExtensionInstructionData = + | { type: 'TokenMetadata'; data: TokenMetadataExtension } + | { type: 'CompressedOnly'; data: CompressedOnlyExtension } + | { type: 'Compressible'; data: CompressionInfo }; + +// ============================================================================ +// TRANSFER2 INSTRUCTION DATA +// ============================================================================ + +/** + * Full Transfer2 instruction data. + */ +export interface Transfer2InstructionData { + /** Whether to include transaction hash in hashing */ + withTransactionHash: boolean; + /** Whether to include lamports change account merkle tree index */ + withLamportsChangeAccountMerkleTreeIndex: boolean; + /** Merkle tree index for lamports change account */ + lamportsChangeAccountMerkleTreeIndex: number; + /** Owner index for lamports change account */ + lamportsChangeAccountOwnerIndex: number; + /** Output queue index */ + outputQueue: number; + /** Maximum top-up for rent */ + maxTopUp: number; + /** CPI context (optional) */ + cpiContext: CompressedCpiContext | null; + /** Compression operations (optional) */ + compressions: Compression[] | null; + /** Validity proof (optional) */ + proof: CompressedProof | null; + /** Input token data */ + inTokenData: MultiInputTokenDataWithContext[]; + /** Output token data */ + outTokenData: MultiTokenTransferOutputData[]; + /** Input lamports (optional) */ + inLamports: bigint[] | null; + /** Output lamports (optional) */ + outLamports: bigint[] | null; + /** Input TLV extensions (optional) */ + inTlv: ExtensionInstructionData[][] | null; + /** Output TLV extensions (optional) */ + outTlv: ExtensionInstructionData[][] | null; +} + +// ============================================================================ +// COMPRESSIBLE CONFIG TYPES +// ============================================================================ + +/** + * Compress-to pubkey configuration. + */ +export interface CompressToPubkey { + /** PDA bump */ + bump: number; + /** Program ID for the PDA */ + programId: Uint8Array; + /** Seeds for the PDA */ + seeds: Uint8Array[]; +} + +/** + * Compressible extension instruction data for create instructions. + */ +export interface CompressibleExtensionInstructionData { + /** Token account version */ + tokenAccountVersion: number; + /** Number of epochs to pre-pay rent */ + rentPayment: number; + /** Compression only mode: 0=false, 1=true */ + compressionOnly: number; + /** Lamports per write for top-up */ + writeTopUp: number; + /** Compress-to pubkey configuration (optional) */ + compressToPubkey: CompressToPubkey | null; +} + +// ============================================================================ +// CREATE ATA TYPES +// ============================================================================ + +/** + * Create Associated Token Account instruction data. + */ +export interface CreateAtaInstructionData { + /** PDA bump */ + bump: number; + /** Compressible config (optional) */ + compressibleConfig: CompressibleExtensionInstructionData | null; +} diff --git a/js/token-sdk/src/constants.ts b/js/token-sdk/src/constants.ts new file mode 100644 index 0000000000..2ce54f7e17 --- /dev/null +++ b/js/token-sdk/src/constants.ts @@ -0,0 +1,180 @@ +/** + * Light Protocol Token SDK Constants + */ + +import { address, type Address } from '@solana/addresses'; + +// ============================================================================ +// PROGRAM IDS +// ============================================================================ + +/** Light Token Program ID */ +export const LIGHT_TOKEN_PROGRAM_ID: Address = address( + 'cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m', +); + +/** Light System Program ID */ +export const LIGHT_SYSTEM_PROGRAM_ID: Address = address( + 'SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7', +); + +/** Account Compression Program ID */ +export const ACCOUNT_COMPRESSION_PROGRAM_ID: Address = address( + 'compr6CUsB5m2jS4Y3831ztGSTnDpnKJTKS95d64XVq', +); + +/** SPL Token Program ID */ +export const SPL_TOKEN_PROGRAM_ID: Address = address( + 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', +); + +/** SPL Token 2022 Program ID */ +export const SPL_TOKEN_2022_PROGRAM_ID: Address = address( + 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb', +); + +/** System Program ID */ +export const SYSTEM_PROGRAM_ID: Address = address( + '11111111111111111111111111111111', +); + +// ============================================================================ +// KNOWN ACCOUNTS +// ============================================================================ + +/** CPI Authority - used for cross-program invocations */ +export const CPI_AUTHORITY: Address = address( + 'GXtd2izAiMJPwMEjfgTRH3d7k9mjn4Jq3JrWFv9gySYy', +); + +/** Mint Address Tree - default tree for compressed mint addresses */ +export const MINT_ADDRESS_TREE: Address = address( + 'amt2kaJA14v3urZbZvnc5v2np8jqvc4Z8zDep5wbtzx', +); + +/** Native Mint (wrapped SOL) */ +export const NATIVE_MINT: Address = address( + 'So11111111111111111111111111111111111111112', +); + +// ============================================================================ +// INSTRUCTION DISCRIMINATORS +// ============================================================================ + +/** + * Instruction discriminators for the Light Token program. + * Uses SPL-compatible values (3-18) plus custom values (100+). + */ +export const DISCRIMINATOR = { + /** CToken transfer between decompressed accounts */ + TRANSFER: 3, + /** Approve delegate on CToken account */ + APPROVE: 4, + /** Revoke delegate on CToken account */ + REVOKE: 5, + /** Mint tokens to CToken account */ + MINT_TO: 7, + /** Burn tokens from CToken account */ + BURN: 8, + /** Close CToken account */ + CLOSE: 9, + /** Freeze CToken account */ + FREEZE: 10, + /** Thaw frozen CToken account */ + THAW: 11, + /** Transfer with decimals validation */ + TRANSFER_CHECKED: 12, + /** Mint with decimals validation */ + MINT_TO_CHECKED: 14, + /** Burn with decimals validation */ + BURN_CHECKED: 15, + /** Create CToken account */ + CREATE_TOKEN_ACCOUNT: 18, + /** Create associated CToken account */ + CREATE_ATA: 100, + /** Batch transfer instruction (compressed/decompressed) */ + TRANSFER2: 101, + /** Create associated CToken account (idempotent) */ + CREATE_ATA_IDEMPOTENT: 102, + /** Batch mint action instruction */ + MINT_ACTION: 103, + /** Claim rent from compressible accounts */ + CLAIM: 104, + /** Withdraw from funding pool */ + WITHDRAW_FUNDING_POOL: 105, +} as const; + +export type Discriminator = (typeof DISCRIMINATOR)[keyof typeof DISCRIMINATOR]; + +// ============================================================================ +// COMPRESSION MODES +// ============================================================================ + +/** + * Compression mode for Transfer2 instruction. + */ +export const COMPRESSION_MODE = { + /** Compress: SPL/CToken -> compressed token */ + COMPRESS: 0, + /** Decompress: compressed token -> SPL/CToken */ + DECOMPRESS: 1, + /** Compress and close the source account */ + COMPRESS_AND_CLOSE: 2, +} as const; + +export type CompressionMode = + (typeof COMPRESSION_MODE)[keyof typeof COMPRESSION_MODE]; + +// ============================================================================ +// EXTENSION DISCRIMINANTS +// ============================================================================ + +/** + * Extension discriminant values for TLV data. + */ +export const EXTENSION_DISCRIMINANT = { + /** Token metadata extension */ + TOKEN_METADATA: 19, + /** CompressedOnly extension */ + COMPRESSED_ONLY: 31, + /** Compressible extension */ + COMPRESSIBLE: 32, +} as const; + +export type ExtensionDiscriminant = + (typeof EXTENSION_DISCRIMINANT)[keyof typeof EXTENSION_DISCRIMINANT]; + +// ============================================================================ +// SEEDS +// ============================================================================ + +/** Compressed mint PDA seed */ +export const COMPRESSED_MINT_SEED = 'compressed_mint'; + +/** Pool PDA seed for SPL interface */ +export const POOL_SEED = 'pool'; + +/** Restricted pool PDA seed */ +export const RESTRICTED_POOL_SEED = 'restricted'; + +// ============================================================================ +// ACCOUNT SIZES +// ============================================================================ + +/** Size of a compressed mint account */ +export const MINT_ACCOUNT_SIZE = 82n; + +/** Base size of a CToken account (without extensions) */ +export const BASE_TOKEN_ACCOUNT_SIZE = 266n; + +/** Extension metadata overhead (Vec length) */ +export const EXTENSION_METADATA_SIZE = 4n; + +/** CompressedOnly extension size */ +export const COMPRESSED_ONLY_EXTENSION_SIZE = 16n; + +/** Transfer fee account extension size */ +export const TRANSFER_FEE_ACCOUNT_EXTENSION_SIZE = 9n; + +/** Transfer hook account extension size */ +export const TRANSFER_HOOK_ACCOUNT_EXTENSION_SIZE = 2n; diff --git a/js/token-sdk/src/generated/.gitkeep b/js/token-sdk/src/generated/.gitkeep new file mode 100644 index 0000000000..32bd937620 --- /dev/null +++ b/js/token-sdk/src/generated/.gitkeep @@ -0,0 +1,2 @@ +# Placeholder for Codama-generated code +# Run `pnpm run generate` in js/token-idl to populate this directory diff --git a/js/token-sdk/src/index.ts b/js/token-sdk/src/index.ts new file mode 100644 index 0000000000..13fd833fe3 --- /dev/null +++ b/js/token-sdk/src/index.ts @@ -0,0 +1,189 @@ +/** + * Light Protocol Token SDK + * + * TypeScript SDK for Light Protocol compressed tokens using Solana Kit (web3.js v2). + * + * @example + * ```typescript + * import { + * createTransferInstruction, + * createAssociatedTokenAccountInstruction, + * deriveAssociatedTokenAddress, + * LIGHT_TOKEN_PROGRAM_ID, + * } from '@lightprotocol/token-sdk'; + * + * // Derive ATA address + * const { address: ata, bump } = await deriveAssociatedTokenAddress(owner, mint); + * + * // Create transfer instruction + * const transferIx = createTransferInstruction({ + * source: sourceAta, + * destination: destAta, + * amount: 1000n, + * authority: owner, + * }); + * ``` + * + * @packageDocumentation + */ + +// ============================================================================ +// CONSTANTS +// ============================================================================ + +export { + // Program IDs + LIGHT_TOKEN_PROGRAM_ID, + LIGHT_SYSTEM_PROGRAM_ID, + ACCOUNT_COMPRESSION_PROGRAM_ID, + SPL_TOKEN_PROGRAM_ID, + SPL_TOKEN_2022_PROGRAM_ID, + SYSTEM_PROGRAM_ID, + + // Known accounts + CPI_AUTHORITY, + MINT_ADDRESS_TREE, + NATIVE_MINT, + + // Instruction discriminators + DISCRIMINATOR, + type Discriminator, + + // Compression modes + COMPRESSION_MODE, + type CompressionMode, + + // Extension discriminants + EXTENSION_DISCRIMINANT, + type ExtensionDiscriminant, + + // Seeds + COMPRESSED_MINT_SEED, + POOL_SEED, + RESTRICTED_POOL_SEED, + + // Account sizes + MINT_ACCOUNT_SIZE, + BASE_TOKEN_ACCOUNT_SIZE, + EXTENSION_METADATA_SIZE, + COMPRESSED_ONLY_EXTENSION_SIZE, + TRANSFER_FEE_ACCOUNT_EXTENSION_SIZE, + TRANSFER_HOOK_ACCOUNT_EXTENSION_SIZE, +} from './constants.js'; + +// ============================================================================ +// UTILITIES +// ============================================================================ + +export { + // PDA derivation + deriveAssociatedTokenAddress, + getAssociatedTokenAddressWithBump, + deriveMintAddress, + derivePoolAddress, + deriveCpiAuthority, + + // Validation + isLightTokenAccount, + determineTransferType, + validateAtaDerivation, + validatePositiveAmount, + validateDecimals, +} from './utils/index.js'; + +// ============================================================================ +// CODECS +// ============================================================================ + +export { + // Types + type Compression, + type PackedMerkleContext, + type MultiInputTokenDataWithContext, + type MultiTokenTransferOutputData, + type CompressedCpiContext, + type CompressedProof, + type TokenMetadataExtension, + type CompressedOnlyExtension, + type RentConfig, + type CompressionInfo, + type ExtensionInstructionData, + type Transfer2InstructionData, + type CompressToPubkey, + type CompressibleExtensionInstructionData, + type CreateAtaInstructionData, + + // Transfer2 codecs + getCompressionCodec, + getPackedMerkleContextCodec, + getMultiInputTokenDataCodec, + getMultiTokenOutputDataCodec, + getCpiContextCodec, + getCompressedProofCodec, + encodeTransfer2InstructionData, + type Transfer2BaseInstructionData, + + // Compressible codecs + getCompressToPubkeyCodec, + getCompressibleExtensionDataCodec, + getCreateAtaDataCodec, + encodeCreateAtaInstructionData, + defaultCompressibleParams, +} from './codecs/index.js'; + +// ============================================================================ +// ACTIONS +// ============================================================================ + +export { + // Transfer actions + createTransferInstruction, + createTransferCheckedInstruction, + createTransferInterfaceInstruction, + requiresCompression, + type TransferParams, + type TransferCheckedParams, + type TransferType, + type TransferInterfaceParams, + type TransferInterfaceResult, + + // Account actions + createAssociatedTokenAccountInstruction, + createAssociatedTokenAccountIdempotentInstruction, + createCloseAccountInstruction, + type CreateAtaParams, + type CreateAtaResult, + type CloseAccountParams, + + // Token operations + createApproveInstruction, + createRevokeInstruction, + createBurnInstruction, + createBurnCheckedInstruction, + createFreezeInstruction, + createThawInstruction, + type ApproveParams, + type RevokeParams, + type BurnParams, + type BurnCheckedParams, + type FreezeParams, + type ThawParams, + + // Mint actions + createMintToInstruction, + createMintToCheckedInstruction, + type MintToParams, + type MintToCheckedParams, +} from './actions/index.js'; + +// ============================================================================ +// RPC CLIENT (Placeholder) +// ============================================================================ + +export { + createLightRpcClient, + isLightRpcAvailable, + type LightRpcClient, + type ParsedTokenAccount, + type ValidityProof, +} from './rpc/index.js'; diff --git a/js/token-sdk/src/rpc/client.ts b/js/token-sdk/src/rpc/client.ts new file mode 100644 index 0000000000..4a64e20a31 --- /dev/null +++ b/js/token-sdk/src/rpc/client.ts @@ -0,0 +1,103 @@ +/** + * Light RPC Client - Placeholder + * + * This module will provide RPC client functionality for querying compressed + * accounts from Photon indexer and requesting validity proofs from the prover. + * + * NOT YET IMPLEMENTED - requires prover integration. + */ + +import type { Address } from '@solana/addresses'; + +// ============================================================================ +// TYPES (Placeholder interfaces for SDK use) +// ============================================================================ + +/** + * Parsed compressed token account. + */ +export interface ParsedTokenAccount { + /** Account hash */ + hash: Uint8Array; + /** Token mint */ + mint: Address; + /** Token owner */ + owner: Address; + /** Token amount */ + amount: bigint; + /** Delegate (if any) */ + delegate: Address | null; + /** Account state */ + state: number; + /** Merkle tree address */ + merkleTree: Address; + /** Leaf index */ + leafIndex: number; +} + +/** + * Validity proof for compressed account operations. + */ +export interface ValidityProof { + /** Groth16 proof element A */ + a: Uint8Array; + /** Groth16 proof element B */ + b: Uint8Array; + /** Groth16 proof element C */ + c: Uint8Array; + /** Root indices */ + rootIndices: number[]; +} + +/** + * Light RPC client interface. + */ +export interface LightRpcClient { + /** Get compressed token accounts by owner */ + getTokenAccountsByOwner( + owner: Address, + mint?: Address, + ): Promise; + /** Get validity proof for account hashes */ + getValidityProof(hashes: Uint8Array[]): Promise; +} + +// ============================================================================ +// FACTORY FUNCTION (Placeholder) +// ============================================================================ + +/** + * Creates a Light RPC client. + * + * @param _endpoint - RPC endpoint (unused in placeholder) + * @returns Never - throws an error + * @throws Error indicating the client is not yet implemented + * + * @example + * ```typescript + * // Future usage: + * const client = createLightRpcClient('https://photon.helius.dev'); + * const accounts = await client.getTokenAccountsByOwner(owner); + * const proof = await client.getValidityProof(accounts.map(a => a.hash)); + * ``` + */ +export function createLightRpcClient(_endpoint: string): LightRpcClient { + throw new Error( + 'Light RPC client is not yet implemented. ' + + 'This feature requires Photon indexer and prover server integration.', + ); +} + +/** + * Checks if Light RPC services are available. + * + * @param _photonUrl - Photon indexer URL (unused in placeholder) + * @param _proverUrl - Prover server URL (unused in placeholder) + * @returns Always false in placeholder + */ +export async function isLightRpcAvailable( + _photonUrl?: string, + _proverUrl?: string, +): Promise { + return false; +} diff --git a/js/token-sdk/src/rpc/index.ts b/js/token-sdk/src/rpc/index.ts new file mode 100644 index 0000000000..2cd06b0f4c --- /dev/null +++ b/js/token-sdk/src/rpc/index.ts @@ -0,0 +1,11 @@ +/** + * Light RPC Client exports. + */ + +export { + createLightRpcClient, + isLightRpcAvailable, + type LightRpcClient, + type ParsedTokenAccount, + type ValidityProof, +} from './client.js'; diff --git a/js/token-sdk/src/utils/derivation.ts b/js/token-sdk/src/utils/derivation.ts new file mode 100644 index 0000000000..0d56d141c3 --- /dev/null +++ b/js/token-sdk/src/utils/derivation.ts @@ -0,0 +1,153 @@ +/** + * PDA derivation utilities for Light Token accounts. + */ + +import { + type Address, + address, + getAddressCodec, + getProgramDerivedAddress, +} from '@solana/addresses'; + +import { + LIGHT_TOKEN_PROGRAM_ID, + COMPRESSED_MINT_SEED, + POOL_SEED, +} from '../constants.js'; + +// ============================================================================ +// ASSOCIATED TOKEN ACCOUNT +// ============================================================================ + +/** + * Derives the associated token account address for a given owner and mint. + * + * Seeds: [owner, LIGHT_TOKEN_PROGRAM_ID, mint] + * + * @param owner - The token account owner + * @param mint - The token mint address + * @returns The derived ATA address and bump + */ +export async function deriveAssociatedTokenAddress( + owner: Address, + mint: Address, +): Promise<{ address: Address; bump: number }> { + const programIdBytes = getAddressCodec().encode(LIGHT_TOKEN_PROGRAM_ID); + + const [derivedAddress, bump] = await getProgramDerivedAddress({ + programAddress: LIGHT_TOKEN_PROGRAM_ID, + seeds: [ + getAddressCodec().encode(owner), + programIdBytes, + getAddressCodec().encode(mint), + ], + }); + + return { address: derivedAddress, bump }; +} + +/** + * Synchronously derives the ATA address (for use when bump is known). + * + * @param owner - The token account owner + * @param mint - The token mint address + * @param bump - The PDA bump seed + * @returns The derived ATA address + */ +export async function getAssociatedTokenAddressWithBump( + owner: Address, + mint: Address, + bump: number, +): Promise
{ + const programIdBytes = getAddressCodec().encode(LIGHT_TOKEN_PROGRAM_ID); + + const [derivedAddress] = await getProgramDerivedAddress({ + programAddress: LIGHT_TOKEN_PROGRAM_ID, + seeds: [ + getAddressCodec().encode(owner), + programIdBytes, + getAddressCodec().encode(mint), + new Uint8Array([bump]), + ], + }); + + return derivedAddress; +} + +// ============================================================================ +// LIGHT MINT +// ============================================================================ + +/** + * Derives the Light mint PDA address from a mint signer. + * + * Seeds: ["compressed_mint", mintSigner] + * + * @param mintSigner - The mint signer/authority pubkey + * @returns The derived mint address and bump + */ +export async function deriveMintAddress( + mintSigner: Address, +): Promise<{ address: Address; bump: number }> { + const [derivedAddress, bump] = await getProgramDerivedAddress({ + programAddress: LIGHT_TOKEN_PROGRAM_ID, + seeds: [ + new TextEncoder().encode(COMPRESSED_MINT_SEED), + getAddressCodec().encode(mintSigner), + ], + }); + + return { address: derivedAddress, bump }; +} + +// ============================================================================ +// SPL INTERFACE POOL +// ============================================================================ + +/** + * Derives the SPL interface pool PDA address. + * + * Seeds: ["pool", mint] or ["pool", mint, index] + * + * @param mint - The token mint address + * @param index - Optional pool index (for multi-pool mints) + * @returns The derived pool address and bump + */ +export async function derivePoolAddress( + mint: Address, + index?: number, +): Promise<{ address: Address; bump: number }> { + const mintBytes = getAddressCodec().encode(mint); + const seeds: Uint8Array[] = [ + new TextEncoder().encode(POOL_SEED), + new Uint8Array(mintBytes), + ]; + + if (index !== undefined) { + // Add index as u16 little-endian + const indexBytes = new Uint8Array(2); + new DataView(indexBytes.buffer).setUint16(0, index, true); + seeds.push(indexBytes); + } + + const [derivedAddress, bump] = await getProgramDerivedAddress({ + programAddress: LIGHT_TOKEN_PROGRAM_ID, + seeds, + }); + + return { address: derivedAddress, bump }; +} + +// ============================================================================ +// CPI AUTHORITY +// ============================================================================ + +/** + * Derives the CPI authority PDA. + * + * @returns The CPI authority address + */ +export async function deriveCpiAuthority(): Promise
{ + // CPI authority is a known constant, but we can derive it for verification + return address('GXtd2izAiMJPwMEjfgTRH3d7k9mjn4Jq3JrWFv9gySYy'); +} diff --git a/js/token-sdk/src/utils/index.ts b/js/token-sdk/src/utils/index.ts new file mode 100644 index 0000000000..28f781ae7e --- /dev/null +++ b/js/token-sdk/src/utils/index.ts @@ -0,0 +1,19 @@ +/** + * Light Token SDK Utilities + */ + +export { + deriveAssociatedTokenAddress, + getAssociatedTokenAddressWithBump, + deriveMintAddress, + derivePoolAddress, + deriveCpiAuthority, +} from './derivation.js'; + +export { + isLightTokenAccount, + determineTransferType, + validateAtaDerivation, + validatePositiveAmount, + validateDecimals, +} from './validation.js'; diff --git a/js/token-sdk/src/utils/validation.ts b/js/token-sdk/src/utils/validation.ts new file mode 100644 index 0000000000..50eb1874fe --- /dev/null +++ b/js/token-sdk/src/utils/validation.ts @@ -0,0 +1,99 @@ +/** + * Validation utilities for Light Token accounts. + */ + +import type { Address } from '@solana/addresses'; +import { LIGHT_TOKEN_PROGRAM_ID } from '../constants.js'; +import { deriveAssociatedTokenAddress } from './derivation.js'; + +// ============================================================================ +// ACCOUNT TYPE DETECTION +// ============================================================================ + +/** + * Checks if an account owner indicates a Light Token account. + * + * @param owner - The account owner address + * @returns True if the owner is the Light Token program + */ +export function isLightTokenAccount(owner: Address): boolean { + return owner === LIGHT_TOKEN_PROGRAM_ID; +} + +/** + * Determines the transfer type based on source and destination owners. + * + * @param sourceOwner - Owner of the source account + * @param destOwner - Owner of the destination account + * @returns The transfer type + */ +export function determineTransferType( + sourceOwner: Address, + destOwner: Address, +): 'light-to-light' | 'light-to-spl' | 'spl-to-light' | 'spl-to-spl' { + const sourceIsLight = isLightTokenAccount(sourceOwner); + const destIsLight = isLightTokenAccount(destOwner); + + if (sourceIsLight && destIsLight) { + return 'light-to-light'; + } + if (sourceIsLight && !destIsLight) { + return 'light-to-spl'; + } + if (!sourceIsLight && destIsLight) { + return 'spl-to-light'; + } + return 'spl-to-spl'; +} + +// ============================================================================ +// ATA VALIDATION +// ============================================================================ + +/** + * Validates that an ATA address matches the expected derivation. + * + * @param ata - The ATA address to validate + * @param owner - The expected owner + * @param mint - The expected mint + * @returns True if the ATA matches the derivation + */ +export async function validateAtaDerivation( + ata: Address, + owner: Address, + mint: Address, +): Promise { + const { address: derivedAta } = await deriveAssociatedTokenAddress( + owner, + mint, + ); + return ata === derivedAta; +} + +// ============================================================================ +// AMOUNT VALIDATION +// ============================================================================ + +/** + * Validates that a transfer amount is positive. + * + * @param amount - The amount to validate + * @throws Error if amount is not positive + */ +export function validatePositiveAmount(amount: bigint): void { + if (amount <= 0n) { + throw new Error('Amount must be positive'); + } +} + +/** + * Validates decimal places for checked operations. + * + * @param decimals - The decimals value (0-255) + * @throws Error if decimals is out of range + */ +export function validateDecimals(decimals: number): void { + if (decimals < 0 || decimals > 255 || !Number.isInteger(decimals)) { + throw new Error('Decimals must be an integer between 0 and 255'); + } +} diff --git a/js/token-sdk/tsconfig.json b/js/token-sdk/tsconfig.json new file mode 100644 index 0000000000..780b37e2d1 --- /dev/null +++ b/js/token-sdk/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "./dist", + "rootDir": "./src", + "resolveJsonModule": true, + "isolatedModules": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "tests"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 158713b83f..a5d7e23c49 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -435,6 +435,91 @@ importers: specifier: ^2.1.1 version: 2.1.1(@types/node@22.16.5)(terser@5.43.1) + js/token-idl: + devDependencies: + '@codama/nodes': + specifier: ^1.4.1 + version: 1.5.0 + '@codama/renderers-js': + specifier: ^1.2.8 + version: 1.5.5(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@codama/visitors': + specifier: ^1.4.1 + version: 1.5.0 + '@codama/visitors-core': + specifier: ^1.4.1 + version: 1.5.0 + '@eslint/js': + specifier: 9.36.0 + version: 9.36.0 + '@typescript-eslint/eslint-plugin': + specifier: ^8.44.0 + version: 8.44.0(@typescript-eslint/parser@8.44.0(eslint@9.36.0)(typescript@5.9.3))(eslint@9.36.0)(typescript@5.9.3) + '@typescript-eslint/parser': + specifier: ^8.44.0 + version: 8.44.0(eslint@9.36.0)(typescript@5.9.3) + codama: + specifier: ^1.4.1 + version: 1.5.0 + eslint: + specifier: ^9.36.0 + version: 9.36.0 + prettier: + specifier: ^3.3.3 + version: 3.6.2 + tsx: + specifier: ^4.19.2 + version: 4.20.5 + typescript: + specifier: ^5.7.3 + version: 5.9.3 + + js/token-sdk: + dependencies: + '@solana/addresses': + specifier: ^2.1.0 + version: 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/codecs': + specifier: ^2.1.0 + version: 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/instructions': + specifier: ^2.1.0 + version: 2.3.0(typescript@5.9.3) + '@solana/keys': + specifier: ^2.1.0 + version: 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/signers': + specifier: ^2.1.0 + version: 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/transaction-messages': + specifier: ^2.1.0 + version: 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + devDependencies: + '@eslint/js': + specifier: 9.36.0 + version: 9.36.0 + '@solana/kit': + specifier: ^2.1.0 + version: 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(ws@8.18.3(bufferutil@4.0.8)(utf-8-validate@5.0.10)) + '@typescript-eslint/eslint-plugin': + specifier: ^8.44.0 + version: 8.44.0(@typescript-eslint/parser@8.44.0(eslint@9.36.0)(typescript@5.9.3))(eslint@9.36.0)(typescript@5.9.3) + '@typescript-eslint/parser': + specifier: ^8.44.0 + version: 8.44.0(eslint@9.36.0)(typescript@5.9.3) + eslint: + specifier: ^9.36.0 + version: 9.36.0 + prettier: + specifier: ^3.3.3 + version: 3.6.2 + typescript: + specifier: ^5.7.3 + version: 5.9.3 + vitest: + specifier: ^2.1.8 + version: 2.1.9(@types/node@22.16.5)(terser@5.43.1) + sdk-tests/sdk-anchor-test: dependencies: '@coral-xyz/anchor': @@ -746,6 +831,36 @@ packages: resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} + '@codama/cli@1.4.4': + resolution: {integrity: sha512-0uLecW/RZC2c1wx3j/eiRAYvilvNY+2DoyEYu/hV0OfM1/uIgIyuy5U+wolV+LY4wLFYdApjYdy+5D32lngCHg==} + hasBin: true + + '@codama/errors@1.5.0': + resolution: {integrity: sha512-i4cS+S7JaZXhofQHFY3cwzt8rqxUVPNaeJND5VOyKUbtcOi933YXJXk52gDG4mc+CpGqHJijsJjfSpr1lJGxzg==} + hasBin: true + + '@codama/node-types@1.5.0': + resolution: {integrity: sha512-Ebz2vOUukmNaFXWdkni1ZihXkAIUnPYtqIMXYxKXOxjMP+TGz2q0lGtRo7sqw1pc2ksFBIkfBp5pZsl5p6gwXA==} + + '@codama/nodes@1.5.0': + resolution: {integrity: sha512-yg+xmorWiMNjS3n19CGIt/FZ/ZCuDIu+HEY45bq6gHu1MN3RtJZY+Q3v0ErnBPA60D8mNWkvkKoeSZXfzcAvfw==} + + '@codama/renderers-core@1.3.5': + resolution: {integrity: sha512-MuZLU+3LZPQb1HuZffwZl+v5JHQDe5LYHGhA1wTMNlwRedYIysSxBjogHNciNIHsKP3JjmqyYmLO5LCEp3hjaQ==} + + '@codama/renderers-js@1.5.5': + resolution: {integrity: sha512-zYVw8KGRHFzrpPKAv8PJI1pMy28qc/iEMspMC6Iw915Vsg0od75FUmUhDAvrTwgc28oyCmlrsWv6BNON4AKmqQ==} + engines: {node: '>=20.18.0'} + + '@codama/validators@1.5.0': + resolution: {integrity: sha512-p3ufDxnCH1jiuHGzcBv4/d+ctzUcKD2K3gX/W8169tC41o9DggjlEpNy1Z6YAAhVb3wHnmXVGA2qmp32rWSfWw==} + + '@codama/visitors-core@1.5.0': + resolution: {integrity: sha512-3PIAlBX0a06hIxzyPtQMfQcqWGFBgfbwysSwcXBbvHUYbemwhD6xwlBKJuqTwm9DyFj3faStp5fpvcp03Rjxtw==} + + '@codama/visitors@1.5.0': + resolution: {integrity: sha512-SwtQaleXxAaFz6uHygxki621q4nPUDQlnwEhsg+QKOjHpKWXjLYdJof+R8gUiTV/n7/IeNnjvxJTTNfUsvETPQ==} + '@coral-xyz/anchor-errors@0.31.1': resolution: {integrity: sha512-NhNEku4F3zzUSBtrYz84FzYWm48+9OvmT1Hhnwr6GnPQry2dsEqH/ti/7ASjjpoFTWRnPXrjAIT1qM6Isop+LQ==} engines: {node: '>=10'} @@ -1865,6 +1980,24 @@ packages: resolution: {integrity: sha512-PJBmyayrlfxM7nbqjomF4YcT1sApQwZio0NHSsT0EzhJqljRmvhzqZua43TyEs80nJk2Cn2FGPg/N8phH6KeCQ==} engines: {node: '>=18.0.0'} + '@solana/accounts@2.3.0': + resolution: {integrity: sha512-QgQTj404Z6PXNOyzaOpSzjgMOuGwG8vC66jSDB+3zHaRcEPRVRd2sVSrd1U6sHtnV3aiaS6YyDuPQMheg4K2jw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + + '@solana/addresses@2.3.0': + resolution: {integrity: sha512-ypTNkY2ZaRFpHLnHAgaW8a83N0/WoqdFvCqf4CQmnMdFsZSdC7qOwcbd7YzdaQn9dy+P2hybewzB+KP7LutxGA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + + '@solana/assertions@2.3.0': + resolution: {integrity: sha512-Ekoet3khNg3XFLN7MIz8W31wPQISpKUGDGTylLptI+JjCDWx3PIa88xjEMqFo02WJ8sBj2NLV64Xg1sBcsHjZQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + '@solana/buffer-layout-utils@0.2.0': resolution: {integrity: sha512-szG4sxgJGktbuZYDg2FfNmkMi0DYQoVjN2h7ta1W1hPrwzarcFLBq9UpX1UjNXsNpT9dn+chgprtWGioUAr4/g==} engines: {node: '>= 10'} @@ -1892,6 +2025,15 @@ packages: peerDependencies: typescript: '>=5.3.3' + '@solana/codecs-core@5.4.0': + resolution: {integrity: sha512-rQ5jXgiDe2vIU+mYCHDjgwMd9WdzZfh4sc5H6JgYleAUjeTUX6mx8hTV2+pcXvvn27LPrgrt9jfxswbDb8O8ww==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@solana/codecs-data-structures@2.0.0-experimental.8618508': resolution: {integrity: sha512-sLpjL9sqzaDdkloBPV61Rht1tgaKq98BCtIKRuyscIrmVPu3wu0Bavk2n/QekmUzaTsj7K1pVSniM0YqCdnEBw==} @@ -1905,6 +2047,12 @@ packages: peerDependencies: typescript: '>=5' + '@solana/codecs-data-structures@2.3.0': + resolution: {integrity: sha512-qvU5LE5DqEdYMYgELRHv+HMOx73sSoV1ZZkwIrclwUmwTbTaH8QAJURBj0RhQ/zCne7VuLLOZFFGv6jGigWhSw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + '@solana/codecs-numbers@2.0.0-experimental.8618508': resolution: {integrity: sha512-EXQKfzFr3CkKKNzKSZPOOOzchXsFe90TVONWsSnVkonO9z+nGKALE0/L9uBmIFGgdzhhU9QQVFvxBMclIDJo2Q==} @@ -1924,6 +2072,15 @@ packages: peerDependencies: typescript: '>=5.3.3' + '@solana/codecs-numbers@5.4.0': + resolution: {integrity: sha512-z6LMkY+kXWx1alrvIDSAxexY5QLhsso638CjM7XI1u6dB7drTLWKhifyjnm1vOQc1VPVFmbYxTgKKpds8TY8tg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@solana/codecs-strings@2.0.0-experimental.8618508': resolution: {integrity: sha512-b2yhinr1+oe+JDmnnsV0641KQqqDG8AQ16Z/x7GVWO+AWHMpRlHWVXOq8U1yhPMA4VXxl7i+D+C6ql0VGFp0GA==} peerDependencies: @@ -1941,6 +2098,25 @@ packages: fastestsmallesttextencoderdecoder: ^1.0.22 typescript: '>=5' + '@solana/codecs-strings@2.3.0': + resolution: {integrity: sha512-y5pSBYwzVziXu521hh+VxqUtp0hYGTl1eWGoc1W+8mdvBdC1kTqm/X7aYQw33J42hw03JjryvYOvmGgk3Qz/Ug==} + engines: {node: '>=20.18.0'} + peerDependencies: + fastestsmallesttextencoderdecoder: ^1.0.22 + typescript: '>=5.3.3' + + '@solana/codecs-strings@5.4.0': + resolution: {integrity: sha512-w0trrjfQDhkCVz7O1GTmHBk9m+MkljKx2uNBbQAD3/yW2Qn9dYiTrZ1/jDVq0/+lPPAUkbT3s3Yo7HUZ2QFmHw==} + engines: {node: '>=20.18.0'} + peerDependencies: + fastestsmallesttextencoderdecoder: ^1.0.22 + typescript: ^5.0.0 + peerDependenciesMeta: + fastestsmallesttextencoderdecoder: + optional: true + typescript: + optional: true + '@solana/codecs@2.0.0-preview.4': resolution: {integrity: sha512-gLMupqI4i+G4uPi2SGF/Tc1aXcviZF2ybC81x7Q/fARamNSgNOCUUoSCg9nWu1Gid6+UhA7LH80sWI8XjKaRog==} peerDependencies: @@ -1951,6 +2127,12 @@ packages: peerDependencies: typescript: '>=5' + '@solana/codecs@2.3.0': + resolution: {integrity: sha512-JVqGPkzoeyU262hJGdH64kNLH0M+Oew2CIPOa/9tR3++q2pEd4jU2Rxdfye9sd0Ce3XJrR5AIa8ZfbyQXzjh+g==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + '@solana/errors@2.0.0-preview.4': resolution: {integrity: sha512-kadtlbRv2LCWr8A9V22On15Us7Nn8BvqNaOB4hXsTB3O0fU40D1ru2l+cReqLcRPij4znqlRzW9Xi0m6J5DIhA==} hasBin: true @@ -1970,6 +2152,52 @@ packages: peerDependencies: typescript: '>=5.3.3' + '@solana/errors@5.4.0': + resolution: {integrity: sha512-hNoAOmlZAszaVBrAy1Jf7amHJ8wnUnTU0BqhNQXknbSvirvsYr81yEud2iq18YiCqhyJ9SuQ5kWrSAT0x7S0oA==} + engines: {node: '>=20.18.0'} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + + '@solana/fast-stable-stringify@2.3.0': + resolution: {integrity: sha512-KfJPrMEieUg6D3hfQACoPy0ukrAV8Kio883llt/8chPEG3FVTX9z/Zuf4O01a15xZmBbmQ7toil2Dp0sxMJSxw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + + '@solana/functional@2.3.0': + resolution: {integrity: sha512-AgsPh3W3tE+nK3eEw/W9qiSfTGwLYEvl0rWaxHht/lRcuDVwfKRzeSa5G79eioWFFqr+pTtoCr3D3OLkwKz02Q==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + + '@solana/instructions@2.3.0': + resolution: {integrity: sha512-PLMsmaIKu7hEAzyElrk2T7JJx4D+9eRwebhFZpy2PXziNSmFF929eRHKUsKqBFM3cYR1Yy3m6roBZfA+bGE/oQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + + '@solana/keys@2.3.0': + resolution: {integrity: sha512-ZVVdga79pNH+2pVcm6fr2sWz9HTwfopDVhYb0Lh3dh+WBmJjwkabXEIHey2rUES7NjFa/G7sV8lrUn/v8LDCCQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + + '@solana/kit@2.3.0': + resolution: {integrity: sha512-sb6PgwoW2LjE5oTFu4lhlS/cGt/NB3YrShEyx7JgWFWysfgLdJnhwWThgwy/4HjNsmtMrQGWVls0yVBHcMvlMQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + + '@solana/nominal-types@2.3.0': + resolution: {integrity: sha512-uKlMnlP4PWW5UTXlhKM8lcgIaNj8dvd8xO4Y9l+FVvh9RvW2TO0GwUO6JCo7JBzCB0PSqRJdWWaQ8pu1Ti/OkA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + '@solana/options@2.0.0-experimental.8618508': resolution: {integrity: sha512-fy/nIRAMC3QHvnKi63KEd86Xr/zFBVxNW4nEpVEU2OT0gCEKwHY4Z55YHf7XujhyuM3PNpiBKg/YYw5QlRU4vg==} @@ -1983,6 +2211,103 @@ packages: peerDependencies: typescript: '>=5' + '@solana/options@2.3.0': + resolution: {integrity: sha512-PPnnZBRCWWoZQ11exPxf//DRzN2C6AoFsDI/u2AsQfYih434/7Kp4XLpfOMT/XESi+gdBMFNNfbES5zg3wAIkw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + + '@solana/programs@2.3.0': + resolution: {integrity: sha512-UXKujV71VCI5uPs+cFdwxybtHZAIZyQkqDiDnmK+DawtOO9mBn4Nimdb/6RjR2CXT78mzO9ZCZ3qfyX+ydcB7w==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + + '@solana/promises@2.3.0': + resolution: {integrity: sha512-GjVgutZKXVuojd9rWy1PuLnfcRfqsaCm7InCiZc8bqmJpoghlyluweNc7ml9Y5yQn1P2IOyzh9+p/77vIyNybQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + + '@solana/rpc-api@2.3.0': + resolution: {integrity: sha512-UUdiRfWoyYhJL9PPvFeJr4aJ554ob2jXcpn4vKmRVn9ire0sCbpQKYx6K8eEKHZWXKrDW8IDspgTl0gT/aJWVg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + + '@solana/rpc-parsed-types@2.3.0': + resolution: {integrity: sha512-B5pHzyEIbBJf9KHej+zdr5ZNAdSvu7WLU2lOUPh81KHdHQs6dEb310LGxcpCc7HVE8IEdO20AbckewDiAN6OCg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + + '@solana/rpc-spec-types@2.3.0': + resolution: {integrity: sha512-xQsb65lahjr8Wc9dMtP7xa0ZmDS8dOE2ncYjlvfyw/h4mpdXTUdrSMi6RtFwX33/rGuztQ7Hwaid5xLNSLvsFQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + + '@solana/rpc-spec@2.3.0': + resolution: {integrity: sha512-fA2LMX4BMixCrNB2n6T83AvjZ3oUQTu7qyPLyt8gHQaoEAXs8k6GZmu6iYcr+FboQCjUmRPgMaABbcr9j2J9Sw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + + '@solana/rpc-subscriptions-api@2.3.0': + resolution: {integrity: sha512-9mCjVbum2Hg9KGX3LKsrI5Xs0KX390lS+Z8qB80bxhar6MJPugqIPH8uRgLhCW9GN3JprAfjRNl7our8CPvsPQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + + '@solana/rpc-subscriptions-channel-websocket@2.3.0': + resolution: {integrity: sha512-2oL6ceFwejIgeWzbNiUHI2tZZnaOxNTSerszcin7wYQwijxtpVgUHiuItM/Y70DQmH9sKhmikQp+dqeGalaJxw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + ws: ^8.18.0 + + '@solana/rpc-subscriptions-spec@2.3.0': + resolution: {integrity: sha512-rdmVcl4PvNKQeA2l8DorIeALCgJEMSu7U8AXJS1PICeb2lQuMeaR+6cs/iowjvIB0lMVjYN2sFf6Q3dJPu6wWg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + + '@solana/rpc-subscriptions@2.3.0': + resolution: {integrity: sha512-Uyr10nZKGVzvCOqwCZgwYrzuoDyUdwtgQRefh13pXIrdo4wYjVmoLykH49Omt6abwStB0a4UL5gX9V4mFdDJZg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + + '@solana/rpc-transformers@2.3.0': + resolution: {integrity: sha512-UuHYK3XEpo9nMXdjyGKkPCOr7WsZsxs7zLYDO1A5ELH3P3JoehvrDegYRAGzBS2VKsfApZ86ZpJToP0K3PhmMA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + + '@solana/rpc-transport-http@2.3.0': + resolution: {integrity: sha512-HFKydmxGw8nAF5N+S0NLnPBDCe5oMDtI2RAmW8DMqP4U3Zxt2XWhvV1SNkAldT5tF0U1vP+is6fHxyhk4xqEvg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + + '@solana/rpc-types@2.3.0': + resolution: {integrity: sha512-O09YX2hED2QUyGxrMOxQ9GzH1LlEwwZWu69QbL4oYmIf6P5dzEEHcqRY6L1LsDVqc/dzAdEs/E1FaPrcIaIIPw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + + '@solana/rpc@2.3.0': + resolution: {integrity: sha512-ZWN76iNQAOCpYC7yKfb3UNLIMZf603JckLKOOLTHuy9MZnTN8XV6uwvDFhf42XvhglgUjGCEnbUqWtxQ9pa/pQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + + '@solana/signers@2.3.0': + resolution: {integrity: sha512-OSv6fGr/MFRx6J+ZChQMRqKNPGGmdjkqarKkRzkwmv7v8quWsIRnJT5EV8tBy3LI4DLO/A8vKiNSPzvm1TdaiQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + '@solana/spl-token-group@0.0.5': resolution: {integrity: sha512-CLJnWEcdoUBpQJfx9WEbX3h6nTdNiUzswfFdkABUik7HVwSNA98u5AYvBVK2H93d9PGMOHAak2lHW9xr+zAJGQ==} engines: {node: '>=16'} @@ -2017,6 +2342,36 @@ packages: resolution: {integrity: sha512-JBMGB0oR4lPttOZ5XiUGyvylwLQjt1CPJa6qQ5oM+MBCndfjz2TKKkw0eATlLLcYmq1jBVsNlJ2cD6ns2GR7lA==} engines: {node: '>=16'} + '@solana/subscribable@2.3.0': + resolution: {integrity: sha512-DkgohEDbMkdTWiKAoatY02Njr56WXx9e/dKKfmne8/Ad6/2llUIrax78nCdlvZW9quXMaXPTxZvdQqo9N669Og==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + + '@solana/sysvars@2.3.0': + resolution: {integrity: sha512-LvjADZrpZ+CnhlHqfI5cmsRzX9Rpyb1Ox2dMHnbsRNzeKAMhu9w4ZBIaeTdO322zsTr509G1B+k2ABD3whvUBA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + + '@solana/transaction-confirmation@2.3.0': + resolution: {integrity: sha512-UiEuiHCfAAZEKdfne/XljFNJbsKAe701UQHKXEInYzIgBjRbvaeYZlBmkkqtxwcasgBTOmEaEKT44J14N9VZDw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + + '@solana/transaction-messages@2.3.0': + resolution: {integrity: sha512-bgqvWuy3MqKS5JdNLH649q+ngiyOu5rGS3DizSnWwYUd76RxZl1kN6CoqHSrrMzFMvis6sck/yPGG3wqrMlAww==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + + '@solana/transactions@2.3.0': + resolution: {integrity: sha512-LnTvdi8QnrQtuEZor5Msje61sDpPstTVwKg4y81tNxDhiyomjuvnSNLAq6QsB9gIxUqbNzPZgOG9IU4I4/Uaug==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + '@solana/web3.js@1.98.4': resolution: {integrity: sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw==} @@ -2227,6 +2582,9 @@ packages: '@vitest/expect@2.1.1': resolution: {integrity: sha512-YeueunS0HiHiQxk+KEOnq/QMzlUuOzbU1Go+PgAsHvvv3tUkJPm9xWt+6ITNTlzsMXUjmgm5T+U7KBPK2qQV6w==} + '@vitest/expect@2.1.9': + resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} + '@vitest/mocker@2.1.1': resolution: {integrity: sha512-LNN5VwOEdJqCmJ/2XJBywB11DLlkbY0ooDJW3uRX5cZyYCrc4PI/ePX0iQhE3BiEGiQmK4GE7Q/PqCkkaiPnrA==} peerDependencies: @@ -2239,21 +2597,47 @@ packages: vite: optional: true + '@vitest/mocker@2.1.9': + resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + '@vitest/pretty-format@2.1.1': resolution: {integrity: sha512-SjxPFOtuINDUW8/UkElJYQSFtnWX7tMksSGW0vfjxMneFqxVr8YJ979QpMbDW7g+BIiq88RAGDjf7en6rvLPPQ==} + '@vitest/pretty-format@2.1.9': + resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} + '@vitest/runner@2.1.1': resolution: {integrity: sha512-uTPuY6PWOYitIkLPidaY5L3t0JJITdGTSwBtwMjKzo5O6RCOEncz9PUN+0pDidX8kTHYjO0EwUIvhlGpnGpxmA==} + '@vitest/runner@2.1.9': + resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==} + '@vitest/snapshot@2.1.1': resolution: {integrity: sha512-BnSku1WFy7r4mm96ha2FzN99AZJgpZOWrAhtQfoxjUU5YMRpq1zmHRq7a5K9/NjqonebO7iVDla+VvZS8BOWMw==} + '@vitest/snapshot@2.1.9': + resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} + '@vitest/spy@2.1.1': resolution: {integrity: sha512-ZM39BnZ9t/xZ/nF4UwRH5il0Sw93QnZXd9NAZGRpIgj0yvVwPpLd702s/Cx955rGaMlyBQkZJ2Ir7qyY48VZ+g==} + '@vitest/spy@2.1.9': + resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} + '@vitest/utils@2.1.1': resolution: {integrity: sha512-Y6Q9TsI+qJ2CC0ZKj6VBb+T8UPz593N113nnUykqwANqhgf3QkZeHFlusgKLTqrnVHbj/XDKZcDHol+dxVT+rQ==} + '@vitest/utils@2.1.9': + resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} + JSONStream@1.3.5: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} hasBin: true @@ -2672,6 +3056,10 @@ packages: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} + codama@1.5.0: + resolution: {integrity: sha512-hhfSzrOiDX3bV7QmJneEBsBk3ln4gIcMJs6P8BlEJ3EFI+P0QZaTT5W61o8Tq0/79hTZeyj0gP65HZ/LYJil+w==} + hasBin: true + code-excerpt@4.0.0: resolution: {integrity: sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -2708,6 +3096,10 @@ packages: resolution: {integrity: sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A==} engines: {node: '>=20'} + commander@14.0.2: + resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==} + engines: {node: '>=20'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -3014,6 +3406,9 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-object-atoms@1.0.0: resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} engines: {node: '>= 0.4'} @@ -3214,6 +3609,10 @@ packages: evp_bytestokey@1.0.3: resolution: {integrity: sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==} + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + eyes@0.1.8: resolution: {integrity: sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==} engines: {node: '> 0.1.90'} @@ -4037,6 +4436,10 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json-stable-stringify@1.3.0: + resolution: {integrity: sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==} + engines: {node: '>= 0.4'} + json-stringify-safe@5.0.1: resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} @@ -4055,6 +4458,9 @@ packages: jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + jsonify@0.0.1: + resolution: {integrity: sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==} + jsonparse@1.3.1: resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} engines: {'0': node >= 0.2.0} @@ -4128,6 +4534,9 @@ packages: magic-string@0.30.11: resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + magic-string@0.30.5: resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==} engines: {node: '>=12'} @@ -5035,6 +5444,9 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + std-env@3.7.0: resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} @@ -5175,6 +5587,9 @@ packages: tinyexec@0.3.0: resolution: {integrity: sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==} + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} @@ -5360,6 +5775,9 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici-types@7.19.0: + resolution: {integrity: sha512-Rjk2OWDZf2eiXVQjY2HyE3XPjqW/wXnSZq0QkOsPKZEnaetNNBObTp91LYfGdB8hRbRZk4HFcM/cONw452B0AQ==} + unicorn-magic@0.3.0: resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} engines: {node: '>=18'} @@ -5424,6 +5842,11 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} hasBin: true + vite-node@2.1.9: + resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + vite@5.0.4: resolution: {integrity: sha512-RzAr8LSvM8lmhB4tQ5OPcBhpjOZRZjuxv9zO5UcxeoY2bd3kP3Ticd40Qma9/BqZ8JS96Ll/jeBX9u+LJZrhVg==} engines: {node: ^18.0.0 || >=20.0.0} @@ -5477,18 +5900,43 @@ packages: jsdom: optional: true - wait-on@7.2.0: - resolution: {integrity: sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==} - engines: {node: '>=12.0.0'} + vitest@2.1.9: + resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} + engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - - wasmbuilder@0.0.16: - resolution: {integrity: sha512-Qx3lEFqaVvp1cEYW7Bfi+ebRJrOiwz2Ieu7ZG2l7YyeSJIok/reEQCQCuicj/Y32ITIJuGIM9xZQppGx5LrQdA==} - - wasmcurves@0.2.2: - resolution: {integrity: sha512-JRY908NkmKjFl4ytnTu5ED6AwPD+8VJ9oc94kdq7h5bIwbj0L4TDJ69mG+2aLs2SoCmGfqIesMWTEJjtYsoQXQ==} - - web-streams-polyfill@3.2.1: + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.9 + '@vitest/ui': 2.1.9 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + wait-on@7.2.0: + resolution: {integrity: sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==} + engines: {node: '>=12.0.0'} + hasBin: true + + wasmbuilder@0.0.16: + resolution: {integrity: sha512-Qx3lEFqaVvp1cEYW7Bfi+ebRJrOiwz2Ieu7ZG2l7YyeSJIok/reEQCQCuicj/Y32ITIJuGIM9xZQppGx5LrQdA==} + + wasmcurves@0.2.2: + resolution: {integrity: sha512-JRY908NkmKjFl4ytnTu5ED6AwPD+8VJ9oc94kdq7h5bIwbj0L4TDJ69mG+2aLs2SoCmGfqIesMWTEJjtYsoQXQ==} + + web-streams-polyfill@3.2.1: resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==} engines: {node: '>= 8'} @@ -6331,6 +6779,65 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@codama/cli@1.4.4': + dependencies: + '@codama/nodes': 1.5.0 + '@codama/visitors': 1.5.0 + '@codama/visitors-core': 1.5.0 + commander: 14.0.2 + picocolors: 1.1.1 + prompts: 2.4.2 + + '@codama/errors@1.5.0': + dependencies: + '@codama/node-types': 1.5.0 + commander: 14.0.2 + picocolors: 1.1.1 + + '@codama/node-types@1.5.0': {} + + '@codama/nodes@1.5.0': + dependencies: + '@codama/errors': 1.5.0 + '@codama/node-types': 1.5.0 + + '@codama/renderers-core@1.3.5': + dependencies: + '@codama/errors': 1.5.0 + '@codama/nodes': 1.5.0 + '@codama/visitors-core': 1.5.0 + + '@codama/renderers-js@1.5.5(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)': + dependencies: + '@codama/errors': 1.5.0 + '@codama/nodes': 1.5.0 + '@codama/renderers-core': 1.3.5 + '@codama/visitors-core': 1.5.0 + '@solana/codecs-strings': 5.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + prettier: 3.6.2 + semver: 7.7.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + - typescript + + '@codama/validators@1.5.0': + dependencies: + '@codama/errors': 1.5.0 + '@codama/nodes': 1.5.0 + '@codama/visitors-core': 1.5.0 + + '@codama/visitors-core@1.5.0': + dependencies: + '@codama/errors': 1.5.0 + '@codama/nodes': 1.5.0 + json-stable-stringify: 1.3.0 + + '@codama/visitors@1.5.0': + dependencies: + '@codama/errors': 1.5.0 + '@codama/nodes': 1.5.0 + '@codama/visitors-core': 1.5.0 + '@coral-xyz/anchor-errors@0.31.1': {} '@coral-xyz/anchor@0.29.0(bufferutil@4.0.8)(typescript@5.9.2)(utf-8-validate@5.0.10)': @@ -7488,6 +7995,34 @@ snapshots: '@smithy/types': 4.5.0 tslib: 2.8.1 + '@solana/accounts@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)': + dependencies: + '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/codecs-core': 2.3.0(typescript@5.9.3) + '@solana/codecs-strings': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/errors': 2.3.0(typescript@5.9.3) + '@solana/rpc-spec': 2.3.0(typescript@5.9.3) + '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/addresses@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)': + dependencies: + '@solana/assertions': 2.3.0(typescript@5.9.3) + '@solana/codecs-core': 2.3.0(typescript@5.9.3) + '@solana/codecs-strings': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/errors': 2.3.0(typescript@5.9.3) + '@solana/nominal-types': 2.3.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/assertions@2.3.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 2.3.0(typescript@5.9.3) + typescript: 5.9.3 + '@solana/buffer-layout-utils@0.2.0(bufferutil@4.0.8)(typescript@5.9.2)(utf-8-validate@5.0.10)': dependencies: '@solana/buffer-layout': 4.0.1 @@ -7526,6 +8061,17 @@ snapshots: '@solana/errors': 2.3.0(typescript@5.9.2) typescript: 5.9.2 + '@solana/codecs-core@2.3.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 2.3.0(typescript@5.9.3) + typescript: 5.9.3 + + '@solana/codecs-core@5.4.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 5.4.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + '@solana/codecs-data-structures@2.0.0-experimental.8618508': dependencies: '@solana/codecs-core': 2.0.0-experimental.8618508 @@ -7545,6 +8091,13 @@ snapshots: '@solana/errors': 2.0.0-rc.1(typescript@5.9.2) typescript: 5.9.2 + '@solana/codecs-data-structures@2.3.0(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 2.3.0(typescript@5.9.3) + '@solana/codecs-numbers': 2.3.0(typescript@5.9.3) + '@solana/errors': 2.3.0(typescript@5.9.3) + typescript: 5.9.3 + '@solana/codecs-numbers@2.0.0-experimental.8618508': dependencies: '@solana/codecs-core': 2.0.0-experimental.8618508 @@ -7573,6 +8126,19 @@ snapshots: '@solana/errors': 2.3.0(typescript@5.9.2) typescript: 5.9.2 + '@solana/codecs-numbers@2.3.0(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 2.3.0(typescript@5.9.3) + '@solana/errors': 2.3.0(typescript@5.9.3) + typescript: 5.9.3 + + '@solana/codecs-numbers@5.4.0(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 5.4.0(typescript@5.9.3) + '@solana/errors': 5.4.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + '@solana/codecs-strings@2.0.0-experimental.8618508(fastestsmallesttextencoderdecoder@1.0.22)': dependencies: '@solana/codecs-core': 2.0.0-experimental.8618508 @@ -7595,6 +8161,23 @@ snapshots: fastestsmallesttextencoderdecoder: 1.0.22 typescript: 5.9.2 + '@solana/codecs-strings@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 2.3.0(typescript@5.9.3) + '@solana/codecs-numbers': 2.3.0(typescript@5.9.3) + '@solana/errors': 2.3.0(typescript@5.9.3) + fastestsmallesttextencoderdecoder: 1.0.22 + typescript: 5.9.3 + + '@solana/codecs-strings@5.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 5.4.0(typescript@5.9.3) + '@solana/codecs-numbers': 5.4.0(typescript@5.9.3) + '@solana/errors': 5.4.0(typescript@5.9.3) + optionalDependencies: + fastestsmallesttextencoderdecoder: 1.0.22 + typescript: 5.9.3 + '@solana/codecs@2.0.0-preview.4(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)': dependencies: '@solana/codecs-core': 2.0.0-preview.4(typescript@5.9.2) @@ -7617,6 +8200,17 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder + '@solana/codecs@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 2.3.0(typescript@5.9.3) + '@solana/codecs-data-structures': 2.3.0(typescript@5.9.3) + '@solana/codecs-numbers': 2.3.0(typescript@5.9.3) + '@solana/codecs-strings': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/options': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + '@solana/errors@2.0.0-preview.4(typescript@5.9.2)': dependencies: chalk: 5.6.2 @@ -7641,6 +8235,73 @@ snapshots: commander: 14.0.1 typescript: 5.9.2 + '@solana/errors@2.3.0(typescript@5.9.3)': + dependencies: + chalk: 5.6.2 + commander: 14.0.1 + typescript: 5.9.3 + + '@solana/errors@5.4.0(typescript@5.9.3)': + dependencies: + chalk: 5.6.2 + commander: 14.0.2 + optionalDependencies: + typescript: 5.9.3 + + '@solana/fast-stable-stringify@2.3.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@solana/functional@2.3.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@solana/instructions@2.3.0(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 2.3.0(typescript@5.9.3) + '@solana/errors': 2.3.0(typescript@5.9.3) + typescript: 5.9.3 + + '@solana/keys@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)': + dependencies: + '@solana/assertions': 2.3.0(typescript@5.9.3) + '@solana/codecs-core': 2.3.0(typescript@5.9.3) + '@solana/codecs-strings': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/errors': 2.3.0(typescript@5.9.3) + '@solana/nominal-types': 2.3.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(ws@8.18.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))': + dependencies: + '@solana/accounts': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/codecs': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/errors': 2.3.0(typescript@5.9.3) + '@solana/functional': 2.3.0(typescript@5.9.3) + '@solana/instructions': 2.3.0(typescript@5.9.3) + '@solana/keys': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/programs': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/rpc': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/rpc-parsed-types': 2.3.0(typescript@5.9.3) + '@solana/rpc-spec-types': 2.3.0(typescript@5.9.3) + '@solana/rpc-subscriptions': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(ws@8.18.3(bufferutil@4.0.8)(utf-8-validate@5.0.10)) + '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/signers': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/sysvars': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/transaction-confirmation': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(ws@8.18.3(bufferutil@4.0.8)(utf-8-validate@5.0.10)) + '@solana/transaction-messages': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/transactions': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + - ws + + '@solana/nominal-types@2.3.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + '@solana/options@2.0.0-experimental.8618508': dependencies: '@solana/codecs-core': 2.0.0-experimental.8618508 @@ -7668,6 +8329,168 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder + '@solana/options@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 2.3.0(typescript@5.9.3) + '@solana/codecs-data-structures': 2.3.0(typescript@5.9.3) + '@solana/codecs-numbers': 2.3.0(typescript@5.9.3) + '@solana/codecs-strings': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/errors': 2.3.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/programs@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)': + dependencies: + '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/errors': 2.3.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/promises@2.3.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@solana/rpc-api@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)': + dependencies: + '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/codecs-core': 2.3.0(typescript@5.9.3) + '@solana/codecs-strings': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/errors': 2.3.0(typescript@5.9.3) + '@solana/keys': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/rpc-parsed-types': 2.3.0(typescript@5.9.3) + '@solana/rpc-spec': 2.3.0(typescript@5.9.3) + '@solana/rpc-transformers': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/transaction-messages': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/transactions': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/rpc-parsed-types@2.3.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@solana/rpc-spec-types@2.3.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@solana/rpc-spec@2.3.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 2.3.0(typescript@5.9.3) + '@solana/rpc-spec-types': 2.3.0(typescript@5.9.3) + typescript: 5.9.3 + + '@solana/rpc-subscriptions-api@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)': + dependencies: + '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/keys': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/rpc-subscriptions-spec': 2.3.0(typescript@5.9.3) + '@solana/rpc-transformers': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/transaction-messages': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/transactions': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/rpc-subscriptions-channel-websocket@2.3.0(typescript@5.9.3)(ws@8.18.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))': + dependencies: + '@solana/errors': 2.3.0(typescript@5.9.3) + '@solana/functional': 2.3.0(typescript@5.9.3) + '@solana/rpc-subscriptions-spec': 2.3.0(typescript@5.9.3) + '@solana/subscribable': 2.3.0(typescript@5.9.3) + typescript: 5.9.3 + ws: 8.18.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) + + '@solana/rpc-subscriptions-spec@2.3.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 2.3.0(typescript@5.9.3) + '@solana/promises': 2.3.0(typescript@5.9.3) + '@solana/rpc-spec-types': 2.3.0(typescript@5.9.3) + '@solana/subscribable': 2.3.0(typescript@5.9.3) + typescript: 5.9.3 + + '@solana/rpc-subscriptions@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(ws@8.18.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))': + dependencies: + '@solana/errors': 2.3.0(typescript@5.9.3) + '@solana/fast-stable-stringify': 2.3.0(typescript@5.9.3) + '@solana/functional': 2.3.0(typescript@5.9.3) + '@solana/promises': 2.3.0(typescript@5.9.3) + '@solana/rpc-spec-types': 2.3.0(typescript@5.9.3) + '@solana/rpc-subscriptions-api': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/rpc-subscriptions-channel-websocket': 2.3.0(typescript@5.9.3)(ws@8.18.3(bufferutil@4.0.8)(utf-8-validate@5.0.10)) + '@solana/rpc-subscriptions-spec': 2.3.0(typescript@5.9.3) + '@solana/rpc-transformers': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/subscribable': 2.3.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + - ws + + '@solana/rpc-transformers@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)': + dependencies: + '@solana/errors': 2.3.0(typescript@5.9.3) + '@solana/functional': 2.3.0(typescript@5.9.3) + '@solana/nominal-types': 2.3.0(typescript@5.9.3) + '@solana/rpc-spec-types': 2.3.0(typescript@5.9.3) + '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/rpc-transport-http@2.3.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 2.3.0(typescript@5.9.3) + '@solana/rpc-spec': 2.3.0(typescript@5.9.3) + '@solana/rpc-spec-types': 2.3.0(typescript@5.9.3) + typescript: 5.9.3 + undici-types: 7.19.0 + + '@solana/rpc-types@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)': + dependencies: + '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/codecs-core': 2.3.0(typescript@5.9.3) + '@solana/codecs-numbers': 2.3.0(typescript@5.9.3) + '@solana/codecs-strings': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/errors': 2.3.0(typescript@5.9.3) + '@solana/nominal-types': 2.3.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/rpc@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)': + dependencies: + '@solana/errors': 2.3.0(typescript@5.9.3) + '@solana/fast-stable-stringify': 2.3.0(typescript@5.9.3) + '@solana/functional': 2.3.0(typescript@5.9.3) + '@solana/rpc-api': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/rpc-spec': 2.3.0(typescript@5.9.3) + '@solana/rpc-spec-types': 2.3.0(typescript@5.9.3) + '@solana/rpc-transformers': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/rpc-transport-http': 2.3.0(typescript@5.9.3) + '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/signers@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)': + dependencies: + '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/codecs-core': 2.3.0(typescript@5.9.3) + '@solana/errors': 2.3.0(typescript@5.9.3) + '@solana/instructions': 2.3.0(typescript@5.9.3) + '@solana/keys': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/nominal-types': 2.3.0(typescript@5.9.3) + '@solana/transaction-messages': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/transactions': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + '@solana/spl-token-group@0.0.5(@solana/web3.js@1.98.4(bufferutil@4.0.8)(typescript@5.9.2)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)': dependencies: '@solana/codecs': 2.0.0-preview.4(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) @@ -7731,6 +8554,71 @@ snapshots: dependencies: buffer: 6.0.3 + '@solana/subscribable@2.3.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 2.3.0(typescript@5.9.3) + typescript: 5.9.3 + + '@solana/sysvars@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)': + dependencies: + '@solana/accounts': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/codecs': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/errors': 2.3.0(typescript@5.9.3) + '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/transaction-confirmation@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(ws@8.18.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))': + dependencies: + '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/codecs-strings': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/errors': 2.3.0(typescript@5.9.3) + '@solana/keys': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/promises': 2.3.0(typescript@5.9.3) + '@solana/rpc': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/rpc-subscriptions': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(ws@8.18.3(bufferutil@4.0.8)(utf-8-validate@5.0.10)) + '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/transaction-messages': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/transactions': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + - ws + + '@solana/transaction-messages@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)': + dependencies: + '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/codecs-core': 2.3.0(typescript@5.9.3) + '@solana/codecs-data-structures': 2.3.0(typescript@5.9.3) + '@solana/codecs-numbers': 2.3.0(typescript@5.9.3) + '@solana/errors': 2.3.0(typescript@5.9.3) + '@solana/functional': 2.3.0(typescript@5.9.3) + '@solana/instructions': 2.3.0(typescript@5.9.3) + '@solana/nominal-types': 2.3.0(typescript@5.9.3) + '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/transactions@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)': + dependencies: + '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/codecs-core': 2.3.0(typescript@5.9.3) + '@solana/codecs-data-structures': 2.3.0(typescript@5.9.3) + '@solana/codecs-numbers': 2.3.0(typescript@5.9.3) + '@solana/codecs-strings': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/errors': 2.3.0(typescript@5.9.3) + '@solana/functional': 2.3.0(typescript@5.9.3) + '@solana/instructions': 2.3.0(typescript@5.9.3) + '@solana/keys': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/nominal-types': 2.3.0(typescript@5.9.3) + '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/transaction-messages': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + '@solana/web3.js@1.98.4(bufferutil@4.0.8)(typescript@5.9.2)(utf-8-validate@5.0.10)': dependencies: '@babel/runtime': 7.25.6 @@ -7907,6 +8795,23 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/eslint-plugin@8.44.0(@typescript-eslint/parser@8.44.0(eslint@9.36.0)(typescript@5.9.3))(eslint@9.36.0)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.44.0(eslint@9.36.0)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.44.0 + '@typescript-eslint/type-utils': 8.44.0(eslint@9.36.0)(typescript@5.9.3) + '@typescript-eslint/utils': 8.44.0(eslint@9.36.0)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.44.0 + eslint: 9.36.0 + graphemer: 1.4.0 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/parser@8.44.0(eslint@9.36.0)(typescript@5.9.2)': dependencies: '@typescript-eslint/scope-manager': 8.44.0 @@ -7919,6 +8824,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/parser@8.44.0(eslint@9.36.0)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.44.0 + '@typescript-eslint/types': 8.44.0 + '@typescript-eslint/typescript-estree': 8.44.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.44.0 + debug: 4.4.3(supports-color@8.1.1) + eslint: 9.36.0 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/project-service@8.44.0(typescript@5.9.2)': dependencies: '@typescript-eslint/tsconfig-utils': 8.44.0(typescript@5.9.2) @@ -7928,6 +8845,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/project-service@8.44.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.44.0(typescript@5.9.3) + '@typescript-eslint/types': 8.44.0 + debug: 4.4.3(supports-color@8.1.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/scope-manager@7.13.1': dependencies: '@typescript-eslint/types': 7.13.1 @@ -7942,6 +8868,10 @@ snapshots: dependencies: typescript: 5.9.2 + '@typescript-eslint/tsconfig-utils@8.44.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + '@typescript-eslint/type-utils@8.44.0(eslint@9.36.0)(typescript@5.9.2)': dependencies: '@typescript-eslint/types': 8.44.0 @@ -7954,6 +8884,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/type-utils@8.44.0(eslint@9.36.0)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.44.0 + '@typescript-eslint/typescript-estree': 8.44.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.44.0(eslint@9.36.0)(typescript@5.9.3) + debug: 4.4.3(supports-color@8.1.1) + eslint: 9.36.0 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/types@7.13.1': {} '@typescript-eslint/types@8.44.0': {} @@ -7989,6 +8931,22 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/typescript-estree@8.44.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.44.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.44.0(typescript@5.9.3) + '@typescript-eslint/types': 8.44.0 + '@typescript-eslint/visitor-keys': 8.44.0 + debug: 4.4.3(supports-color@8.1.1) + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.2 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/utils@7.13.1(eslint@9.36.0)(typescript@5.9.2)': dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@9.36.0) @@ -8011,6 +8969,17 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/utils@8.44.0(eslint@9.36.0)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0) + '@typescript-eslint/scope-manager': 8.44.0 + '@typescript-eslint/types': 8.44.0 + '@typescript-eslint/typescript-estree': 8.44.0(typescript@5.9.3) + eslint: 9.36.0 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/visitor-keys@7.13.1': dependencies: '@typescript-eslint/types': 7.13.1 @@ -8028,6 +8997,13 @@ snapshots: chai: 5.3.3 tinyrainbow: 1.2.0 + '@vitest/expect@2.1.9': + dependencies: + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + tinyrainbow: 1.2.0 + '@vitest/mocker@2.1.1(@vitest/spy@2.1.1)(vite@5.0.4(@types/node@22.16.5)(terser@5.43.1))': dependencies: '@vitest/spy': 2.1.1 @@ -8036,31 +9012,64 @@ snapshots: optionalDependencies: vite: 5.0.4(@types/node@22.16.5)(terser@5.43.1) + '@vitest/mocker@2.1.9(vite@5.0.4(@types/node@22.16.5)(terser@5.43.1))': + dependencies: + '@vitest/spy': 2.1.9 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 5.0.4(@types/node@22.16.5)(terser@5.43.1) + '@vitest/pretty-format@2.1.1': dependencies: tinyrainbow: 1.2.0 + '@vitest/pretty-format@2.1.9': + dependencies: + tinyrainbow: 1.2.0 + '@vitest/runner@2.1.1': dependencies: '@vitest/utils': 2.1.1 pathe: 1.1.2 + '@vitest/runner@2.1.9': + dependencies: + '@vitest/utils': 2.1.9 + pathe: 1.1.2 + '@vitest/snapshot@2.1.1': dependencies: '@vitest/pretty-format': 2.1.1 magic-string: 0.30.11 pathe: 1.1.2 + '@vitest/snapshot@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + magic-string: 0.30.21 + pathe: 1.1.2 + '@vitest/spy@2.1.1': dependencies: tinyspy: 3.0.2 + '@vitest/spy@2.1.9': + dependencies: + tinyspy: 3.0.2 + '@vitest/utils@2.1.1': dependencies: '@vitest/pretty-format': 2.1.1 loupe: 3.2.0 tinyrainbow: 1.2.0 + '@vitest/utils@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + loupe: 3.2.0 + tinyrainbow: 1.2.0 + JSONStream@1.3.5: dependencies: jsonparse: 1.3.1 @@ -8538,6 +9547,14 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + codama@1.5.0: + dependencies: + '@codama/cli': 1.4.4 + '@codama/errors': 1.5.0 + '@codama/nodes': 1.5.0 + '@codama/validators': 1.5.0 + '@codama/visitors': 1.5.0 + code-excerpt@4.0.0: dependencies: convert-to-spaces: 2.0.1 @@ -8568,6 +9585,8 @@ snapshots: commander@14.0.1: {} + commander@14.0.2: {} + commander@2.20.3: {} commander@5.1.0: {} @@ -9008,6 +10027,8 @@ snapshots: es-errors@1.3.0: {} + es-module-lexer@1.7.0: {} + es-object-atoms@1.0.0: dependencies: es-errors: 1.3.0 @@ -9295,6 +10316,8 @@ snapshots: md5.js: 1.3.5 safe-buffer: 5.2.1 + expect-type@1.3.0: {} + eyes@0.1.8: {} fast-check@3.23.2: @@ -10187,6 +11210,14 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + json-stable-stringify@1.3.0: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + isarray: 2.0.5 + jsonify: 0.0.1 + object-keys: 1.1.1 + json-stringify-safe@5.0.1: {} json5@1.0.2: @@ -10201,6 +11232,8 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + jsonify@0.0.1: {} + jsonparse@1.3.1: {} keyv@4.5.4: @@ -10266,6 +11299,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + magic-string@0.30.5: dependencies: '@jridgewell/sourcemap-codec': 1.4.15 @@ -11192,6 +12229,8 @@ snapshots: stackback@0.0.2: {} + std-env@3.10.0: {} + std-env@3.7.0: {} stdin-discarder@0.2.2: {} @@ -11363,6 +12402,8 @@ snapshots: tinyexec@0.3.0: {} + tinyexec@0.3.2: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) @@ -11392,6 +12433,10 @@ snapshots: dependencies: typescript: 5.9.2 + ts-api-utils@2.1.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + ts-mocha@10.1.0(mocha@11.7.5): dependencies: mocha: 11.7.5 @@ -11587,6 +12632,8 @@ snapshots: undici-types@6.21.0: {} + undici-types@7.19.0: {} + unicorn-magic@0.3.0: {} union@0.5.0: @@ -11653,6 +12700,23 @@ snapshots: - supports-color - terser + vite-node@2.1.9(@types/node@22.16.5)(terser@5.43.1): + dependencies: + cac: 6.7.14 + debug: 4.4.3(supports-color@8.1.1) + es-module-lexer: 1.7.0 + pathe: 1.1.2 + vite: 5.0.4(@types/node@22.16.5)(terser@5.43.1) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + vite@5.0.4(@types/node@22.16.5)(terser@5.43.1): dependencies: esbuild: 0.19.5 @@ -11696,6 +12760,40 @@ snapshots: - supports-color - terser + vitest@2.1.9(@types/node@22.16.5)(terser@5.43.1): + dependencies: + '@vitest/expect': 2.1.9 + '@vitest/mocker': 2.1.9(vite@5.0.4(@types/node@22.16.5)(terser@5.43.1)) + '@vitest/pretty-format': 2.1.9 + '@vitest/runner': 2.1.9 + '@vitest/snapshot': 2.1.9 + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + debug: 4.4.3(supports-color@8.1.1) + expect-type: 1.3.0 + magic-string: 0.30.21 + pathe: 1.1.2 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.0.1 + tinyrainbow: 1.2.0 + vite: 5.0.4(@types/node@22.16.5)(terser@5.43.1) + vite-node: 2.1.9(@types/node@22.16.5)(terser@5.43.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.16.5 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - stylus + - sugarss + - supports-color + - terser + wait-on@7.2.0: dependencies: axios: 1.12.2 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 3a1786cae1..3fec7b2697 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -5,4 +5,6 @@ packages: - "sdk-tests/sdk-anchor-test/**" - "js/stateless.js/**" - "js/compressed-token/**" + - "js/token-idl/**" + - "js/token-sdk/**" - "examples/**" diff --git a/scripts/lint.sh b/scripts/lint.sh index a12aaf558e..0808acaedc 100755 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -5,6 +5,8 @@ set -e # JS linting cd js/stateless.js && pnpm prettier --check . && pnpm lint && cd ../.. cd js/compressed-token && pnpm prettier --check . && pnpm lint && cd ../.. +cd js/token-idl && pnpm prettier --check . && pnpm lint && cd ../.. +cd js/token-sdk && pnpm prettier --check . && pnpm lint && cd ../.. # Rust linting cargo +nightly fmt --all -- --check