Skip to main content
Glama

OpenZeppelin Contracts MCP Server

Official
by OpenZeppelin
print.ts11.3 kB
import type { Contract, Argument, ContractFunction, TraitImplBlock, UseClause } from './contract'; import type { Lines } from './utils/format-lines'; import { formatLines, spaceBetween } from './utils/format-lines'; import { getSelfArg } from './common-options'; import { compatibleContractsSemver } from './utils/version'; import { toByteArray } from './utils/convert-strings'; const DEFAULT_SECTION = '1. with no section'; const STANDALONE_IMPORTS_GROUP = 'Standalone Imports'; const MAX_USE_CLAUSE_LINE_LENGTH = 90; const TAB = ' '; export const createLevelAttributes = [`#![no_std]`]; export const removeCreateLevelAttributes = (printedContract: string) => createLevelAttributes.reduce( (cleanedPrintedContract, createLevelAttribute) => cleanedPrintedContract.replace(createLevelAttribute, ''), printedContract, ); export function printContract(contract: Contract): string { return formatLines( ...spaceBetween( [ `// SPDX-License-Identifier: ${contract.license}`, `// Compatible with OpenZeppelin Stellar Soroban Contracts ${compatibleContractsSemver}`, ...(contract.documentations.length ? ['', ...printDocumentations(contract.documentations)] : []), ...createLevelAttributes, ], spaceBetween( printUseClauses(contract), printMetadata(contract), printVariables(contract), printContractStruct(contract), printContractErrors(contract), printContractFunctions(contract), printImplementedTraits(contract), ), ), ); } function printContractStruct(contract: Contract): Lines[] { const lines = []; if (contract.derives.length > 0) { lines.push(`#[derive(${contract.derives.sort().join(', ')})]`); } lines.push('#[contract]'); lines.push(`pub struct ${contract.name};`); return lines; } function printContractErrors(contract: Contract): Lines[] { if (contract.errors.length === 0) { return []; } return [ '#[contracterror]', '#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]', '#[repr(u32)]', `pub enum ${contract.name}Error {`, contract.errors.map(e => `${e.name} = ${e.num},`), `}`, ]; } function withSemicolons(lines: string[]): string[] { return lines.map(line => (line.endsWith(';') ? line : line + ';')); } function printVariables(contract: Contract): string[] { return withSemicolons(contract.variables.map(v => `const ${v.name}: ${v.type} = ${v.value}`)); } function printUseClauses(contract: Contract): Lines[] { const useClauses = sortUseClauses(contract); // group by containerPath const grouped = useClauses.reduce((result: { [containerPath: string]: UseClause[] }, useClause: UseClause) => { if (useClause.groupable) { (result[useClause.containerPath] = result[useClause.containerPath] || []).push(useClause); } else { (result[STANDALONE_IMPORTS_GROUP] = result[STANDALONE_IMPORTS_GROUP] || []).push(useClause); } return result; }, {}); const lines = Object.entries(grouped).flatMap(([groupName, group]) => getLinesFromUseClausesGroup(group, groupName)); return lines.flatMap(line => splitLongUseClauseLine(line.toString())); } function getLinesFromUseClausesGroup(group: UseClause[], groupName: string): Lines[] { const lines = []; if (groupName === STANDALONE_IMPORTS_GROUP) { for (const useClause of group) { lines.push(`use ${useClause.containerPath}::${nameWithAlias(useClause)};`); } } else { if (group.length == 1) { lines.push(`use ${groupName}::${nameWithAlias(group[0]!)};`); } else if (group.length > 1) { const names = group.map(useClause => nameWithAlias(useClause)).join(', '); lines.push(`use ${groupName}::{${names}};`); } } return lines; } function nameWithAlias(useClause: UseClause): string { return useClause.alias ? `${useClause.name} as ${useClause.alias}` : useClause.name; } // TODO: remove this when we can use a formatting js library function splitLongUseClauseLine(line: string): Lines[] { const lines = []; const containsBraces = line.indexOf('{') !== -1; if (containsBraces && line.length > MAX_USE_CLAUSE_LINE_LENGTH) { // split at the first brace lines.push(line.slice(0, line.indexOf('{') + 1)); lines.push(...splitLongLineInner(line.slice(line.indexOf('{') + 1, -2))); lines.push('};'); } else { lines.push(line); } return lines; } function splitLongLineInner(line: string): Lines[] { const lines = []; if (line.length > MAX_USE_CLAUSE_LINE_LENGTH) { const max_accessible_string = line.slice(0, MAX_USE_CLAUSE_LINE_LENGTH); const lastCommaIndex = max_accessible_string.lastIndexOf(','); if (lastCommaIndex !== -1) { lines.push(TAB + max_accessible_string.slice(0, lastCommaIndex + 1)); lines.push(...splitLongLineInner(line.slice(lastCommaIndex + 2))); } else { lines.push(TAB + max_accessible_string); } } else { lines.push(TAB + line); } return lines; } function sortUseClauses(contract: Contract): UseClause[] { return contract.useClauses.sort((a, b) => { if (a.containerPath === b.containerPath) { if (a.name === 'self' && b.name !== 'self') { return -1; } else if (a.name !== 'self' && b.name === 'self') { return 1; } return nameWithAlias(a).localeCompare(nameWithAlias(b)); } else { return a.containerPath.localeCompare(b.containerPath); } }); } function printImplementedTraits(contract: Contract): Lines[] { // sort first by priority, then number of tags, then name const sortedTraits = contract.implementedTraits.sort((a, b) => { if (a.priority !== b.priority) { return (a.priority ?? Infinity) - (b.priority ?? Infinity); } if (a.tags.length !== b.tags.length) { return a.tags.length - b.tags.length; } return a.traitName.localeCompare(b.traitName); }); // group by section const grouped = sortedTraits.reduce((result: { [section: string]: TraitImplBlock[] }, current: TraitImplBlock) => { // default to no section const section = current.section ?? DEFAULT_SECTION; (result[section] = result[section] || []).push(current); return result; }, {}); const sections = Object.entries(grouped) .sort((a, b) => a[0].localeCompare(b[0])) .map(([section, impls]) => printImplementedTraitsSection(section, impls as TraitImplBlock[])); return spaceBetween(...sections); } function printImplementedTraitsSection(section: string, impls: TraitImplBlock[]): Lines[] { const lines = []; const isDefaultSection = section === DEFAULT_SECTION; if (!isDefaultSection) { lines.push('//'); lines.push(`// ${section}`); lines.push('//'); } impls.forEach((trait, index) => { if (index > 0 || !isDefaultSection) { lines.push(''); } lines.push(...printImplementedTrait(trait)); }); return lines; } function printImplementedTrait(trait: TraitImplBlock): Lines[] { const implLines = []; implLines.push(...trait.tags.map(t => `#[${t}]`)); const head = `impl ${trait.traitName} for ${trait.structName}`; const assocTypeLines = trait.assocType !== undefined && trait.assocType.trim().length > 0 ? [TAB + trait.assocType.trim(), ''] : []; const functionLines = trait.functions.map(fn => printFunction(fn)); const hasBodyContent = assocTypeLines.length > 0 || functionLines.length > 0; if (!hasBodyContent) { // Empty impl implLines.push(`${head} {}`); } else { implLines.push(`${head} {`); if (assocTypeLines.length > 0) { implLines.push(...assocTypeLines); } if (functionLines.length > 0) { implLines.push(spaceBetween(...functionLines)); } implLines.push('}'); } return implLines; } function printFunction(fn: ContractFunction): Lines[] { const head = `fn ${fn.name}`; const args = fn.args.map(a => printArgument(a)); const codeLines = fn.codeBefore?.concat(fn.code) ?? fn.code; for (let i = 0; i < codeLines.length; i++) { const line = codeLines[i]; const shouldEndWithSemicolon = i < codeLines.length - 1 || fn.returns === undefined; if (line !== undefined && line.length > 0) { if (shouldEndWithSemicolon && !['{', '}', ';'].includes(line.charAt(line.length - 1))) { codeLines[i] += ';'; } else if (!shouldEndWithSemicolon && line.endsWith(';')) { codeLines[i] = line.slice(0, line.length - 1); } } } return printFunction2(fn.pub, head, args, fn.tags, fn.returns, undefined, codeLines); } function printContractFunctions(contract: Contract): Lines[] { const constructorLines = printConstructor(contract); const freeFunctionLines = contract.freeFunctions.map(fn => printFunction(fn)); if (constructorLines.length === 0 && freeFunctionLines.length === 0) { return []; } const implBlock: Lines[] = ['#[contractimpl]', `impl ${contract.name} {`]; if (constructorLines.length > 0) { implBlock.push(constructorLines); } if (constructorLines.length > 0 && freeFunctionLines.length > 0) { // Add line break between constructor and first free function implBlock.push(''); } if (freeFunctionLines.length > 0) { implBlock.push(spaceBetween(...freeFunctionLines)); } implBlock.push('}'); return implBlock; } function printConstructor(contract: Contract): Lines[] { if (contract.constructorCode.length > 0) { const head = 'fn __constructor'; const args = [getSelfArg(), ...contract.constructorArgs]; const body = spaceBetween(withSemicolons(contract.constructorCode)); const constructor = printFunction2( true, head, args.map(a => printArgument(a)), [], undefined, undefined, body, ); return constructor; } else { return []; } } // generic for functions and constructors // kindedName = 'fn foo' function printFunction2( pub: boolean | undefined, kindedName: string, args: string[], tags: string[], returns: string | undefined, returnLine: string | undefined, code: Lines[], ): Lines[] { const fn = []; for (let i = 0; i < tags.length; i++) { fn.push(`#[${tags[i]}]`); } let accum = ''; if (pub) { accum += 'pub '; } accum += `${kindedName}(`; if (args.length > 0) { const formattedArgs = args.join(', '); if (formattedArgs.length > 80) { fn.push(accum); accum = ''; // print each arg in a separate line fn.push(args.map(arg => `${arg},`)); } else { accum += `${formattedArgs}`; } } accum += ')'; if (returns === undefined) { accum += ' {'; } else { accum += ` -> ${returns} {`; } fn.push(accum); fn.push(code); if (returnLine !== undefined) { fn.push([returnLine]); } fn.push('}'); return fn; } function printArgument(arg: Argument): string { if (arg.type !== undefined) { const type = arg.type; return `${arg.name}: ${type}`; } else { return `${arg.name}`; } } function printDocumentations(documentations: string[]): string[] { return documentations.map(documentation => `//! ${documentation}`); } function printMetadata(contract: Contract) { return Array.from(contract.metadata.entries()).map(([key, value]) => `contractmeta!(key="${key}", val="${value}");`); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/OpenZeppelin/contracts-wizard'

If you have feedback or need assistance with the MCP directory API, please join our Discord server