UNPKG

@eliseev_s/tolk-tlb-transpiler

Version:

Transpile Tolk structs to TLB definitions and generate TypeScript wrappers for TON blockchain smart contracts

1,101 lines 47.3 kB
"use strict"; // ============================================================================ // TypeScript Wrapper Generator - Generate Smart Contract Wrapper Classes // ============================================================================ Object.defineProperty(exports, "__esModule", { value: true }); exports.WrapperGenerator = void 0; const tlb_codegen_1 = require("@eliseev_s/tlb-codegen"); const constants_1 = require("./constants"); const type_converters_1 = require("./type-converters"); class WrapperGenerator { config; additionalImports = []; utilityTypes = []; wrapperClass = ''; typescriptCode = ''; constructor(config) { this.config = config; } /** * Create and initialize a ready-to-use WrapperGenerator instance * * @param config - Configuration for the wrapper generator * @returns Promise resolving to initialized WrapperGenerator instance */ static async create(config) { const generator = new WrapperGenerator(config); await generator.initialize(); generator.generateImports().generateUtilityTypes().generateWrapperClass(); return generator; } /** * Initialize wrapper generator with TypeScript code generation */ async initialize() { const tlbString = this.formatTlbDefinitions(); this.typescriptCode = (0, tlb_codegen_1.generateCode)(constants_1.COMMON_TLB_DEFINITIONS + tlbString, 'typescript'); } /** * Format TLB definitions into a readable string format */ formatTlbDefinitions() { const output = []; output.push('// ============================================================================'); output.push(`// TLB Schema for ${this.config.contractName}`); output.push('// ============================================================================'); output.push(''); for (const definition of this.config.tlbDefinitions) { output.push(`// ${definition.name}`); for (const constructor of definition.statement) { output.push(constructor); } output.push(''); } return output.join('\n'); } /** * Generate additional imports needed for wrapper */ generateImports() { this.additionalImports = []; return this; } /** * Generate utility types for send methods */ generateUtilityTypes() { const { incomingMessageType, structures, typeAliases, getMethods } = this.config; // Extract send methods to generate types for them const sendMethods = WrapperGenerator.extractSendMethods(incomingMessageType, structures, typeAliases); const utils = [ "// Utility type to exclude 'kind' field from send method parameters", "type OmitKind<T> = T extends { kind: any } ? Omit<T, 'kind'> : T;", '', '// Helper function to parse maybe cell with custom parser', 'export function parseMaybeCell<T>(cell: Cell | null, parser: (slice: Slice) => T): T | null {', ' return cell === null ? null : parser(cell.beginParse());', '}', '', '// Helper function to read tuple items from TupleReader', 'function readTupleItems(reader: TupleReader): TupleItem[] {', ' const tuple = reader.readTuple();', ' const items: TupleItem[] = [];', ' while (tuple.remaining > 0) {', ' items.push(tuple.pop());', ' }', ' return items;', '}', '', '// Helper function to read optional tuple items from TupleReader', 'function readTupleItemsOpt(reader: TupleReader): TupleItem[] | null {', ' const tuple = reader.readTupleOpt();', ' if (tuple === null) {', ' return null;', ' }', ' const items: TupleItem[] = [];', ' while (tuple.remaining > 0) {', ' items.push(tuple.pop());', ' }', ' return items;', '}', '', '// Body types for send methods', ...sendMethods.map((method) => `export type ${method.messageType}Body = OmitKind<${method.messageType}>;`), ]; // Collect all map<K,V> usages to generate reusable codecs and loaders const allMaps = WrapperGenerator.collectAllMapTypes(structures, getMethods); const uniqueValueTypes = new Map(); const uniqueMaps = new Map(); for (const m of allMaps) { const vId = (0, type_converters_1.getTypeIdentifier)(m.value); if (!uniqueValueTypes.has(vId)) uniqueValueTypes.set(vId, m.value); const mapId = `${(0, type_converters_1.getTypeIdentifier)(m.key)}__${vId}`; if (!uniqueMaps.has(mapId)) uniqueMaps.set(mapId, { key: m.key, value: m.value }); } if (uniqueValueTypes.size > 0) { utils.push(''); utils.push('// Dictionary value codecs for map value types'); for (const [id, valueType] of uniqueValueTypes.entries()) { const tsType = (0, type_converters_1.tolkTypeToTSType)(valueType); const codecExpr = WrapperGenerator.generateDictValueCodec(valueType, structures); utils.push(`export const dictValue_${id}: DictionaryValue<${tsType}> = ${codecExpr};`); } } if (uniqueMaps.size > 0) { utils.push(''); utils.push('// Helpers to load dictionaries from raw cells'); for (const { key, value } of uniqueMaps.values()) { const keyInfo = (0, type_converters_1.generateDictKeyDescriptor)(key); const keyTs = keyInfo.ts; // Use the correct TS type from keyInfo (number for <=32 bits, bigint for >32) const valueTs = (0, type_converters_1.tolkTypeToTSType)(value); const loaderName = (0, type_converters_1.getDictLoaderName)(key, value); const vId = (0, type_converters_1.getTypeIdentifier)(value); utils.push(`export function ${loaderName}(cell: Cell | null): Dictionary<${keyTs}, ${valueTs}> { return Dictionary.loadDirect<${keyTs}, ${valueTs}>(${keyInfo.expr}, dictValue_${vId}, cell); }`); } } this.utilityTypes = utils; return this; } /** * Generate wrapper class */ generateWrapperClass() { const { contractName, storageType, incomingMessageType, internalMessageType, getMethods, structures, typeAliases, } = this.config; // Extract send methods from incoming message union const sendMethods = WrapperGenerator.extractSendMethods(incomingMessageType, structures, typeAliases); // Extract opcodes from internal message union (all messages) const opcodesMap = WrapperGenerator.extractOpcodesMap(internalMessageType, structures, typeAliases); const parts = [ WrapperGenerator.generateClassDeclaration(contractName), WrapperGenerator.generateOpcodesMap(opcodesMap), WrapperGenerator.generateCreateFromAddress(contractName), WrapperGenerator.generateCreateFromConfig(storageType, contractName), WrapperGenerator.generateSendDeploy(), ...sendMethods.map((method) => WrapperGenerator.generateCreateBodyMethod(method)), ...sendMethods.map((method) => WrapperGenerator.generateSendMethod(method, contractName)), ...getMethods.map((method) => WrapperGenerator.generateGetMethod(method, structures)), WrapperGenerator.generateClassEnd(), ]; this.wrapperClass = parts.join('\n\n'); return this; } /** * Render the complete wrapper code */ render() { // Parse TypeScript code to find import section const lines = this.typescriptCode.split('\n'); const importEndIndex = lines.findIndex((line) => line.trim() === '' && lines.slice(0, lines.indexOf(line)).some((l) => l.startsWith('import'))); let fullCode; if (importEndIndex > 0) { // Collect all @ton/core imports and merge them into one const allCoreImports = new Set(); const coreImportIndices = []; lines.forEach((line, idx) => { if (line.startsWith('import ') && line.includes("'@ton/core'")) { coreImportIndices.push(idx); const match = line.match(/\{([^}]+)\}/); if (match && match[1]) { match[1] .split(',') .map((s) => s.trim()) .filter(Boolean) .forEach((s) => allCoreImports.add(s)); } } }); // Add required symbols const needed = [ 'Contract', 'contractAddress', 'ContractProvider', 'Sender', 'SendMode', 'TupleItem', 'TupleReader', ]; needed.forEach((s) => allCoreImports.add(s)); if (coreImportIndices.length > 0) { // Remove all @ton/core imports except the first one for (let i = coreImportIndices.length - 1; i > 0; i--) { lines.splice(coreImportIndices[i], 1); } // Update the first one with all merged imports const merged = Array.from(allCoreImports).sort(); lines[coreImportIndices[0]] = `import { ${merged.join(', ')} } from '@ton/core';`; } else { // No @ton/core import found, add one lines.splice(0, 0, "import { Contract, contractAddress, ContractProvider, Sender, SendMode, Dictionary, DictionaryValue, Slice, Cell, beginCell, Address, TupleItem } from '@ton/core';"); } // No additional imports needed; core import was merged above const modifiedTypescriptCode = lines.join('\n'); // Combine with utility types and wrapper class const parts = [ modifiedTypescriptCode, this.utilityTypes.join('\n'), this.wrapperClass, ].filter((part) => part.trim()); fullCode = parts.join('\n\n'); } else { // Fallback: combine all parts const parts = [ this.typescriptCode, "import { Contract, contractAddress, ContractProvider, Sender, SendMode, Dictionary, Slice, Cell, beginCell, Address, TupleItem } from '@ton/core';", this.utilityTypes.join('\n'), this.wrapperClass, ].filter((part) => part.trim()); fullCode = parts.join('\n\n'); } return fullCode; } /** * Get the generated TypeScript code */ getTypescriptCode() { return this.typescriptCode; } /** * Get the generated wrapper class code */ getWrapperCode() { return this.wrapperClass; } /** * Get the formatted TLB definitions */ getTlbString() { return this.formatTlbDefinitions(); } /** * Generate additional imports needed for wrapper (avoiding duplicates) */ static generateAdditionalImports() { // Only add imports that are not already present in TLB codegen output return `import { Contract, contractAddress, ContractProvider, Sender, SendMode } from '@ton/core';`; } /** * Generate utility types for send methods */ static generateUtilityTypes() { return `// Utility type to exclude 'kind' field from send method parameters type OmitKind<T> = T extends { kind: any } ? Omit<T, 'kind'> : T;`; } /** * Generate class declaration */ static generateClassDeclaration(contractName) { return `export class ${contractName} implements Contract { constructor(readonly address: Address, readonly init?: { code: Cell; data: Cell }) {}`; } /** * Generate createFromAddress method */ static generateCreateFromAddress(contractName) { return ` static createFromAddress(address: Address) { return new ${contractName}(address); }`; } /** * Generate createFromConfig method */ static generateCreateFromConfig(storageType, contractName) { return ` static createFromConfig(storage: ${storageType}, code: Cell, workchain = 0) { const data = beginCell().store(store${storageType}(storage)).endCell(); const init = { code, data }; return new ${contractName}(contractAddress(workchain, init), init); }`; } /** * Generate sendDeploy method */ static generateSendDeploy() { return ` async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) { await provider.internal(via, { value, sendMode: SendMode.PAY_GAS_SEPARATELY, body: beginCell().endCell(), }); }`; } /** * Generate send method for internal message */ static generateSendMethod(method, contractName) { const bodyTypeName = `${method.messageType}Body`; const createBodyMethodName = `create${method.messageType}Body`; return ` async ${method.name}( provider: ContractProvider, via: Sender, value: bigint, params: ${bodyTypeName} ) { await provider.internal(via, { value, sendMode: SendMode.PAY_GAS_SEPARATELY, body: ${contractName}.${createBodyMethodName}(params), }); }`; } /** * Generate static create body method for internal message */ static generateCreateBodyMethod(method) { const bodyTypeName = `${method.messageType}Body`; const createBodyMethodName = `create${method.messageType}Body`; return ` static ${createBodyMethodName}(params: ${bodyTypeName}) { return beginCell().store(store${method.messageType}({ ...params, kind: '${method.messageType}' })).endCell(); }`; } /** * Generate get method */ static generateGetMethod(method, structures) { // Normalize the method name (convert snake_case to camelCase) const normalizedName = WrapperGenerator.normalizeMethodName(method.name); // If the original method already starts with 'get', don't add another 'get' prefix const methodName = normalizedName.startsWith('get') ? normalizedName : `get${WrapperGenerator.capitalize(normalizedName)}`; const returnType = (0, type_converters_1.tolkTypeToTSType)(method.returnType); let parameters = ''; let args = ''; if (method.parameters.length > 0) { const paramStrings = method.parameters.map((param) => `${param.name}: ${(0, type_converters_1.tolkTypeToTSType)(param.type)}`); parameters = `, ${paramStrings.join(', ')}`; // Flatten struct parameters into individual stack arguments const argStrings = []; for (const param of method.parameters) { const args = WrapperGenerator.generateStackArgumentsFlat(param.type, param.name, structures); argStrings.push(...args); } args = `, [${argStrings.join(',\n ')}]`; } else { // For methods without parameters, pass empty array args = ', []'; } return ` async ${methodName}(provider: ContractProvider${parameters}): Promise<${returnType}> { const { stack } = await provider.get('${method.name}'${args}); return ${WrapperGenerator.generateStackReader(method.returnType, structures)}; }`; } /** * Generate flat list of stack arguments for a parameter type. * Structs are flattened into sequential arguments for each field. * Nested structs are also flattened recursively. */ static generateStackArgumentsFlat(type, paramName, structures) { // Check if this is a struct type if (type.kind === 'type_identifier') { const struct = structures.find((s) => s.name === type.name); if (struct) { // Flatten struct fields into sequential stack arguments const args = []; for (const field of struct.fields) { const fieldAccess = `${paramName}.${field.name}`; args.push(...WrapperGenerator.generateStackArgumentsFlat(field.type, fieldAccess, structures)); } return args; } } // For non-struct types, return a single argument return [WrapperGenerator.generateStackArgument(type, paramName)]; } /** * Generate stack argument for get method parameter (single atomic type) */ static generateStackArgument(type, paramName) { const paramType = (0, type_converters_1.tolkTypeToTSType)(type); // Handle untyped tuple (TupleItem[]) if (paramType === 'TupleItem[]') { return `{ type: 'tuple', items: ${paramName}, }`; } else if (paramType === 'TupleItem[] | null') { // Handle optional untyped tuple (tuple?) return `${paramName} === null ? { type: 'null', } : { type: 'tuple', items: ${paramName}, }`; } else if (paramType === 'Address') { return `{ type: 'slice', cell: beginCell().storeAddress(${paramName}).endCell(), }`; } else if (paramType === 'bigint' || paramType === 'number') { // All integers passed as stack int - always wrap in BigInt() for safety return `{ type: 'int', value: BigInt(${paramName}), }`; } else if (paramType === 'boolean') { return `{ type: 'int', value: ${paramName} ? 1n : 0n, }`; } else if (paramType === 'Cell') { // Handle cell parameter - just pass the cell directly return `{ type: 'cell', cell: ${paramName}, }`; } else if (paramType === 'Cell | null') { // Handle dict/map type return `${paramName} === null ? { type: 'null', } : { type: 'cell', cell: ${paramName}, }`; } else if (type.kind === 'primitive') { switch (type.name) { case 'int': return `{ type: 'cell', cell: beginCell().storeInt(${paramName}, 257).endCell(), }`; case 'uint': return `{ type: 'cell', cell: beginCell().storeUint(${paramName}, 256).endCell(), }`; case 'bool': return `{ type: 'cell', cell: beginCell().storeUint(${paramName} ? 1 : 0, 1).endCell(), }`; case 'address': return `{ type: 'slice', cell: beginCell().storeAddress(${paramName}).endCell(), }`; case 'coins': return `{ type: 'cell', cell: beginCell().storeCoins(${paramName}).endCell(), }`; case 'cell': // This case should not be reached since Cell parameters are handled above return `{ type: 'cell', cell: ${paramName}, }`; case 'slice': return `{ type: 'slice', cell: ${paramName}, }`; default: return `{ type: 'cell', cell: beginCell().storeRef(${paramName}).endCell(), }`; } } else if (type.kind === 'int' || type.kind === 'uint') { // Pass integers directly on the stack - always wrap in BigInt() return `{ type: 'int', value: BigInt(${paramName}), }`; } else if (type.kind === 'type_identifier') { // Handle common type identifiers that are parsed as type_identifier instead of primitive switch (type.name) { case 'int': return `{ type: 'cell', cell: beginCell().storeInt(${paramName}, 257).endCell(), }`; case 'uint': return `{ type: 'cell', cell: beginCell().storeUint(${paramName}, 256).endCell(), }`; case 'bool': return `{ type: 'cell', cell: beginCell().storeUint(${paramName} ? 1 : 0, 1).endCell(), }`; case 'address': return `{ type: 'slice', cell: beginCell().storeAddress(${paramName}).endCell(), }`; case 'coins': return `{ type: 'cell', cell: beginCell().storeCoins(${paramName}).endCell(), }`; case 'cell': return `{ type: 'cell', cell: ${paramName}, }`; case 'slice': return `{ type: 'slice', cell: ${paramName}, }`; case 'dict': return `${paramName} === null ? { type: 'null', } : { type: 'cell', cell: ${paramName}, }`; default: // For custom types, use the appropriate store function return `{ type: 'cell', cell: beginCell().store(store${(0, type_converters_1.tolkTypeToStoreName)(type)}(${paramName})).endCell(), }`; } } else { // For other custom types, use the appropriate store function return `{ type: 'cell', cell: beginCell().store(store${(0, type_converters_1.tolkTypeToStoreName)(type)}(${paramName})).endCell(), }`; } } /** * Generate stack reader for return type */ static generateStackReader(returnType, structures) { switch (returnType.kind) { case 'primitive': switch (returnType.name) { case 'void': return 'undefined'; case 'bool': return 'stack.readBoolean()'; case 'int': case 'uint': case 'coins': return 'stack.readBigNumber()'; case 'address': return 'stack.readAddress()'; case 'cell': case 'slice': return 'stack.readCell()'; default: return 'stack.readCell()'; } case 'int': case 'uint': return 'stack.readBigNumber()'; case 'dict': return 'stack.readCellOpt()'; case 'map': { const keyTs = (0, type_converters_1.tolkTypeToTSType)(returnType.key); const valueTs = (0, type_converters_1.tolkTypeToTSType)(returnType.value); const loaderName = (0, type_converters_1.getDictLoaderName)(returnType.key, returnType.value); return `(() => { const dictCell = stack.readCellOpt(); return ${loaderName}(dictCell); })()`; } case 'special': switch (returnType.name) { default: return 'stack.readCell()'; } case 'type_identifier': // Handle void type if (returnType.name === 'void') { return 'undefined'; } // Handle untyped tuple (tuple keyword without parameters) if (returnType.name === 'tuple') { return 'readTupleItems(stack)'; } // Handle common type identifiers switch (returnType.name) { case 'int': return 'stack.readBigNumber()'; case 'uint': return 'stack.readBigNumber()'; case 'bool': case 'Bool': return 'stack.readBoolean()'; case 'address': return 'stack.readAddress()'; case 'cell': return 'stack.readCell()'; case 'coins': return 'stack.readBigNumber()'; case 'slice': return 'stack.readCell()'; default: // Check if this is a struct type const struct = structures.find((s) => s.name === returnType.name); if (struct) { return WrapperGenerator.generateStackReaderForStruct(struct, structures); } // For other custom types, assume they need cell parsing return 'stack.readCell()'; } case 'tensor': // Handle tensor returns as tuple - read each element from stack const tensorReaders = returnType.items.map((item) => WrapperGenerator.generateStackReader(item, structures)); return `[${tensorReaders.join(', ')}]`; case 'tuple': // Handle tuple returns as tuple - read each element from stack const tupleReaders = returnType.items.map((item) => WrapperGenerator.generateStackReader(item, structures)); return `[${tupleReaders.join(', ')}]`; case 'nullable': { const inner = returnType.inner; if (inner.kind === 'primitive') { switch (inner.name) { case 'bool': return 'stack.readBooleanOpt()'; case 'int': case 'uint': case 'coins': return 'stack.readBigNumberOpt()'; case 'address': return 'stack.readAddressOpt()'; case 'cell': case 'slice': return 'stack.readCellOpt()'; default: return 'stack.readCellOpt()'; } } if (inner.kind === 'uint' || inner.kind === 'int') { if (inner.width <= 32) return 'stack.readNumberOpt()'; return 'stack.readBigNumberOpt()'; } if (inner.kind === 'type_identifier') { // Handle optional untyped tuple (tuple?) if (inner.name === 'tuple') { return 'readTupleItemsOpt(stack)'; } switch (inner.name) { case 'bool': case 'Bool': return 'stack.readBooleanOpt()'; case 'int': case 'uint': case 'coins': return 'stack.readBigNumberOpt()'; case 'address': return 'stack.readAddressOpt()'; case 'cell': case 'slice': return 'stack.readCellOpt()'; default: { const struct = structures.find((s) => s.name === inner.name); if (struct) { return `parseMaybeCell(stack.readCellOpt(), load${inner.name})`; } return 'stack.readCellOpt()'; } } } return 'stack.readCellOpt()'; } default: return 'stack.readCell()'; } } /** * Generate stack reader for struct type */ static generateStackReaderForStruct(struct, structures) { // Generate the field readers const fieldReaders = struct.fields.map((field) => { const reader = WrapperGenerator.generateStructFieldStackReader(field.type, structures); return `${field.name}: ${reader}`; }); // Create the object with kind field return `{ kind: '${struct.name}', ${fieldReaders.join(',\n ')} }`; } /** * Generate stack reader for struct field type (handles special cases for struct compatibility) */ static generateStructFieldStackReader(fieldType, structures) { switch (fieldType.kind) { case 'primitive': switch (fieldType.name) { case 'bool': // Bool now maps to boolean return 'stack.readBoolean()'; case 'int': case 'uint': // For general int/uint, use bigint return 'stack.readBigNumber()'; case 'coins': return 'stack.readBigNumber()'; case 'address': return 'stack.readAddress()'; case 'cell': case 'slice': return 'stack.readCell()'; default: return 'stack.readCell()'; } case 'uint': if (fieldType.width <= 32) { return 'stack.readNumber()'; } else { return 'stack.readBigNumber()'; } case 'int': if (fieldType.width <= 32) { return 'stack.readNumber()'; } else { return 'stack.readBigNumber()'; } case 'dict': // Dict and map are represented as Cell | null return 'stack.readCellOpt()'; case 'map': { const keyTs = (0, type_converters_1.tolkTypeToTSType)(fieldType.key); const valueTs = (0, type_converters_1.tolkTypeToTSType)(fieldType.value); const loaderName = (0, type_converters_1.getDictLoaderName)(fieldType.key, fieldType.value); return `${loaderName}(stack.readCellOpt())`; } case 'nullable': // Handle nullable types as value | null return WrapperGenerator.generateNullableFieldReader(fieldType.inner, structures); case 'type_identifier': switch (fieldType.name) { case 'int': case 'uint': case 'coins': return 'stack.readBigNumber()'; case 'bool': case 'Bool': return 'stack.readBoolean()'; case 'address': return 'stack.readAddress()'; case 'cell': case 'slice': return 'stack.readCell()'; default: const struct = structures.find((s) => s.name === fieldType.name); if (struct) { return WrapperGenerator.generateStackReaderForStruct(struct, structures); } return 'stack.readCell()'; } default: return WrapperGenerator.generateStackReader(fieldType, structures); } } /** * Generate reader for nullable field using loadMaybe */ static generateNullableFieldReader(innerType, structures) { switch (innerType.kind) { case 'primitive': switch (innerType.name) { case 'bool': return 'stack.readBooleanOpt()'; case 'int': case 'uint': case 'coins': return 'stack.readBigNumberOpt()'; case 'address': return 'stack.readAddressOpt()'; case 'cell': case 'slice': return 'stack.readCellOpt()'; default: return 'stack.readCellOpt()'; } case 'uint': if (innerType.width <= 32) return 'stack.readNumberOpt()'; return 'stack.readBigNumberOpt()'; case 'int': if (innerType.width <= 32) return 'stack.readNumberOpt()'; return 'stack.readBigNumberOpt()'; case 'type_identifier': // Handle common type identifiers switch (innerType.name) { case 'int': case 'uint': case 'coins': return 'stack.readBigNumberOpt()'; case 'bool': case 'Bool': return 'stack.readBooleanOpt()'; case 'address': return 'stack.readAddressOpt()'; case 'cell': case 'slice': return 'stack.readCellOpt()'; default: // Check if this is a struct type const struct = structures.find((s) => s.name === innerType.name); if (struct) { return `parseMaybeCell(stack.readCellOpt(), load${innerType.name})`; } return 'stack.readCellOpt()'; } default: return 'stack.readCellOpt()'; } } /** * Extract send methods from internal message union type name */ static extractSendMethods(internalMessageType, structures, typeAliases) { // Find the type alias for the internal message type const unionTypeAlias = typeAliases.find((alias) => alias.name === internalMessageType); if (!unionTypeAlias) { return []; } const sendMethods = []; // Helper function to process a single message type const processMessageType = (messageStructName) => { // Find the corresponding struct const messageStruct = structures.find((struct) => struct.name === messageStructName); if (messageStruct) { // Convert message struct name to send method name // e.g., "increment_message" -> "sendIncrement", "IncrementMessage" -> "sendIncrement" let methodName = messageStruct.name; if (methodName.endsWith('Message')) { methodName = methodName.slice(0, -7); // Remove "Message" } // Normalize the method name (convert snake_case to camelCase) const normalizedName = WrapperGenerator.normalizeMethodName(methodName); methodName = `send${WrapperGenerator.capitalize(normalizedName)}`; const sendMethod = { name: methodName, messageType: messageStruct.name, parameters: messageStruct.fields, }; sendMethods.push(sendMethod); } }; // Handle union type with multiple alternatives if (unionTypeAlias.underlyingType.kind === 'union') { const alternatives = unionTypeAlias.underlyingType.alternatives; for (const alternative of alternatives) { if (alternative.kind === 'type_identifier') { processMessageType(alternative.name); } } } // Handle single message type (not a union) else if (unionTypeAlias.underlyingType.kind === 'type_identifier') { processMessageType(unionTypeAlias.underlyingType.name); } return sendMethods; } /** * Generate class end */ static generateClassEnd() { return '}'; } /** * Capitalize first letter */ static capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); } /** * Convert snake_case to camelCase */ static snakeToCamelCase(str) { return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()); } /** * Convert method name to camelCase (handles both snake_case and PascalCase) */ static normalizeMethodName(methodName) { // First convert snake_case to camelCase let normalized = WrapperGenerator.snakeToCamelCase(methodName); // If the result starts with uppercase, make it lowercase (PascalCase -> camelCase) if (normalized.charAt(0) === normalized.charAt(0).toUpperCase()) { normalized = normalized.charAt(0).toLowerCase() + normalized.slice(1); } return normalized; } /** * Extract opcodes map from internal message union type */ static extractOpcodesMap(internalMessageType, structures, typeAliases) { // Find the type alias for the internal message type const unionTypeAlias = typeAliases.find((alias) => alias.name === internalMessageType); if (!unionTypeAlias) { return {}; } const opcodesMap = {}; // Helper function to process a single message type const processMessageType = (messageStructName) => { // Find the corresponding struct const messageStruct = structures.find((struct) => struct.name === messageStructName); if (messageStruct && messageStruct.prefix) { opcodesMap[messageStruct.name] = messageStruct.prefix.value; } }; // Handle union type with multiple alternatives if (unionTypeAlias.underlyingType.kind === 'union') { const alternatives = unionTypeAlias.underlyingType.alternatives; for (const alternative of alternatives) { if (alternative.kind === 'type_identifier') { processMessageType(alternative.name); } } } // Handle single message type (not a union) else if (unionTypeAlias.underlyingType.kind === 'type_identifier') { processMessageType(unionTypeAlias.underlyingType.name); } return opcodesMap; } /** * Generate static opcodes map */ static generateOpcodesMap(opcodesMap) { const entries = Object.entries(opcodesMap); if (entries.length === 0) { return ' static readonly opcodes = {} as const;'; } const opcodesEntries = entries .map(([messageName, opcode]) => ` ${messageName}: ${opcode}`) .join(',\n'); return ` static readonly opcodes = { ${opcodesEntries} } as const;`; } // Helpers for Dictionary codecs static generateDictValueCodec(valueType, structures) { const tsType = (0, type_converters_1.tolkTypeToTSType)(valueType); // Primitive codecs if (valueType.kind === 'primitive') { switch (valueType.name) { case 'bool': return `{ serialize: (src: boolean, b) => b.storeUint(src ? 1 : 0, 1), parse: (s: Slice) => s.loadUint(1) === 1 }`; case 'int': return `{ serialize: (src: bigint, b) => b.storeInt(src, 257), parse: (s: Slice) => s.loadIntBig(257) }`; case 'uint': return `{ serialize: (src: bigint, b) => b.storeUint(src, 256), parse: (s: Slice) => s.loadUintBig(256) }`; case 'coins': return `{ serialize: (src: bigint, b) => b.storeCoins(src), parse: (s: Slice) => s.loadCoins() }`; case 'address': return `{ serialize: (src: Address | null, b) => b.storeAddress(src), parse: (s: Slice) => s.loadAddressAny() }`; case 'cell': return `{ serialize: (src: Cell, b) => b.storeSlice(src.beginParse(true)), parse: (s: Slice) => s.asCell() }`; case 'slice': return `{ serialize: (src: Cell, b) => b.storeSlice(src.beginParse(true)), parse: (s: Slice) => s.asCell() }`; default: { const mU = valueType.name.match(/^uint(\d+)$/); if (mU && mU[1]) { const w = parseInt(mU[1], 10); const loadExpr = w <= 32 ? `s.loadUint(${w})` : `s.loadUintBig(${w})`; return `{ serialize: (src: ${w <= 32 ? 'number' : 'bigint'}, b) => b.storeUint(src as bigint, ${w}), parse: (s: Slice) => ${loadExpr} }`; } const mI = valueType.name.match(/^int(\d+)$/); if (mI && mI[1]) { const w = parseInt(mI[1], 10); const loadExpr = w <= 32 ? `s.loadInt(${w})` : `s.loadIntBig(${w})`; return `{ serialize: (src: ${w <= 32 ? 'number' : 'bigint'}, b) => b.storeInt(src as bigint, ${w}), parse: (s: Slice) => ${loadExpr} }`; } return `{ serialize: (src: ${tsType}, b) => b.storeRef(beginCell().storeRef(src as any).endCell()), parse: (s: Slice) => s.asCell() as any }`; } } } if (valueType.kind === 'uint') { const isSmall = valueType.width <= 32; const srcTs = isSmall ? 'number' : 'bigint'; const loadExpr = isSmall ? `s.loadUint(${valueType.width})` : `s.loadUintBig(${valueType.width})`; const storeExpr = isSmall ? `b.storeUint(BigInt(src), ${valueType.width})` : `b.storeUint(src, ${valueType.width})`; return `{ serialize: (src: ${srcTs}, b) => ${storeExpr}, parse: (s: Slice) => ${loadExpr} }`; } if (valueType.kind === 'int') { const isSmall = valueType.width <= 32; const srcTs = isSmall ? 'number' : 'bigint'; const loadExpr = isSmall ? `s.loadInt(${valueType.width})` : `s.loadIntBig(${valueType.width})`; const storeExpr = isSmall ? `b.storeInt(BigInt(src), ${valueType.width})` : `b.storeInt(src, ${valueType.width})`; return `{ serialize: (src: ${srcTs}, b) => ${storeExpr}, parse: (s: Slice) => ${loadExpr} }`; } if (valueType.kind === 'type_identifier') { const struct = structures.find((s) => s.name === valueType.name); if (struct) { // Struct values are stored inline in the dictionary cell, not as a reference return `{ serialize: (src: ${tsType}, b) => b.store(store${struct.name}(src)), parse: (s: Slice) => load${struct.name}(s) }`; } if (valueType.name === 'bool' || valueType.name === 'Bool') { return `{ serialize: (src: boolean, b) => b.storeUint(src ? 1 : 0, 1), parse: (s: Slice) => s.loadUint(1) === 1 }`; } if (valueType.name === 'int' || valueType.name === 'uint') { return `{ serialize: (src: bigint, b) => b.storeUint(src, 256), parse: (s: Slice) => s.loadUintBig(256) }`; } if (valueType.name === 'coins') { return `{ serialize: (src: bigint, b) => b.storeCoins(src), parse: (s: Slice) => s.loadCoins() }`; } if (valueType.name === 'address') { return `{ serialize: (src: Address | null, b) => b.storeAddress(src), parse: (s: Slice) => s.loadAddressAny() }`; } if (valueType.name === 'cell' || valueType.name === 'slice') { return `{ serialize: (src: Cell, b) => b.storeSlice(src.beginParse(true)), parse: (s: Slice) => s.asCell() }`; } } // Fallback generic cell return `{ serialize: (src: ${tsType}, b) => b.storeRef(beginCell().endCell()), parse: (s: Slice) => s.asCell() as any }`; } // Collect map types used in structs (fields) and get method return/params static collectAllMapTypes(structs, getMethods) { const maps = []; const visit = (t) => { if (!t) return; switch (t.kind) { case 'map': maps.push({ key: t.key, value: t.value }); visit(t.key); visit(t.value); break; case 'nullable': visit(t.inner); break; case 'tensor': case 'tuple': for (const it of t.items) visit(it); break; case 'union': for (const it of t.alternatives) visit(it); break; case 'generic': for (const it of t.params) visit(it); break; case 'cell_ref': if (t.inner) visit(t.inner); break; default: break; } }; for (const s of structs) { for (const f of s.fields) visit(f.type); } for (const gm of getMethods) { for (const p of gm.parameters) visit(p.type); visit(gm.returnType); } return maps; } } exports.WrapperGenerator = WrapperGenerator; //# sourceMappingURL=wrapper-generator.js.map