@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
JavaScript
"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