navskit
Version:
Deploy TypeScript logic on Ethereum. Includes core library, CLI tools, and utilities.
787 lines (755 loc) • 35.6 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.NavsGenerator = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const deploy_1 = require("../../deploy");
const viem_1 = require("viem");
class NavsGenerator {
targetDir;
isLocal;
constructor(targetDir, isLocal = false) {
this.targetDir = path.resolve(targetDir);
this.isLocal = isLocal;
}
async generate() {
const { packageJson } = this.validateProjectStructure();
const functions = this.extractNavsFunctions();
if (functions.length === 0) {
console.log('⚠️ No @navs functions found - skipping Solidity contract generation');
return;
}
this.generateSolidityLibrary(functions, packageJson);
}
validateProjectStructure() {
const packageJsonPath = path.join(this.targetDir, 'package.json');
if (!fs.existsSync(packageJsonPath)) {
throw new Error('No package.json found in target directory');
}
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
const main = packageJson.main || 'index.js';
return { main, packageJson };
}
extractNavsFunctions() {
return this.performStaticAnalysis();
}
performStaticAnalysis() {
// Read the source files and look for @navs decorators
const srcDir = path.join(this.targetDir, 'src');
const functions = [];
if (fs.existsSync(srcDir)) {
const files = this.findTypeScriptFiles(srcDir);
for (const file of files) {
const content = fs.readFileSync(file, 'utf8');
// Check if file contains @navs
if (content.includes('@navs')) {
const extractedFunctions = this.extractFunctionsFromFile(content, file);
functions.push(...extractedFunctions);
}
}
}
console.log(`📄 Found ${functions.length} functions`);
return functions;
}
findTypeScriptFiles(dir) {
const files = [];
function walkDir(currentDir) {
const items = fs.readdirSync(currentDir);
for (const item of items) {
const fullPath = path.join(currentDir, item);
const stat = fs.statSync(fullPath);
if (stat.isDirectory() && item !== 'node_modules' && item !== 'dist') {
walkDir(fullPath);
}
else if (stat.isFile() && item.endsWith('.ts')) {
files.push(fullPath);
}
}
}
walkDir(dir);
return files;
}
extractFunctionsFromFile(content, filename) {
const functions = [];
// Improved regex to find @navs decorated functions
// This matches: @navs(optional_params) static/async? functionName(params): ReturnType
const navsRegex = /\s*(\([^)]*\))?\s*(?:static\s+)?(?:async\s+)?(\w+)\s*\([^)]*\)\s*:\s*(?:Promise<([^>]+)>|([^\s{]+))/g;
let match;
while ((match = navsRegex.exec(content)) !== null) {
const navsParams = match[1]; // The parameters inside @navs(...)
const functionName = match[2];
const promiseReturnType = match[3]; // From Promise<Type>
const directReturnType = match[4]; // Direct return type
const returnType = promiseReturnType || directReturnType || 'unknown';
// Determine consensus type based on @navs parameters
const consensusType = this.determineConsensusType(navsParams);
if (functionName) {
functions.push({
name: functionName,
parameters: this.extractParametersFromFunction(content, functionName),
returnType: this.mapTypeScriptToSolidity(returnType),
description: `Function ${functionName} from ${path.basename(filename)}`,
consensusType
});
}
}
// Also try a simpler approach - look for lines with @navs followed by function declarations
const lines = content.split('\n');
for (let i = 0; i < lines.length - 1; i++) {
const line = lines[i].trim();
if (line.includes('@navs')) {
const navsMatch = line.match(/\s*(\([^)]*\))?/);
const navsParams = navsMatch ? navsMatch[1] : null;
const consensusType = this.determineConsensusType(navsParams);
const nextLine = lines[i + 1];
const funcMatch = nextLine.match(/(?:static\s+)?(?:async\s+)?(\w+)\s*\([^)]*\)\s*:\s*(?:Promise<([^>]+)>|([^\s{]+))/);
if (funcMatch) {
const functionName = funcMatch[1];
const promiseReturnType = funcMatch[2];
const directReturnType = funcMatch[3];
const returnType = promiseReturnType || directReturnType || 'unknown';
// Check if we already found this function
if (!functions.some(f => f.name === functionName)) {
functions.push({
name: functionName,
parameters: this.extractParametersFromFunction(content, functionName),
returnType: this.mapTypeScriptToSolidity(returnType),
description: `Function ${functionName} from ${path.basename(filename)}`,
consensusType
});
}
}
}
}
return functions;
}
determineConsensusType(navsParams) {
// If @navs() has no parameters, use EXACT_MATCH
if (!navsParams || navsParams === '()') {
return 'EXACT_MATCH';
}
// If @navs has parameters (like consensus.numericalAverage), use CUSTOM
return 'CUSTOM';
}
extractParametersFromFunction(content, functionName) {
// Try to extract parameters from function signature, being more specific to avoid comments
// Look for the pattern that starts a line (or after whitespace) with static/async keywords
const funcRegex = new RegExp(`(?:^|\\n)\\s*(?:static\\s+)?(?:async\\s+)?${functionName}\\s*\\(([^)]*)\\)\\s*:`, 'm');
const match = funcRegex.exec(content);
if (match && match[1]) {
const paramString = match[1].trim();
if (paramString) {
const params = paramString.split(',').map(p => p.trim());
return params.map((param, index) => {
const colonIndex = param.indexOf(':');
if (colonIndex > 0) {
const name = param.substring(0, colonIndex).trim();
const type = param.substring(colonIndex + 1).trim();
const safeName = this.getSafeParameterName(name || `param${index + 1}`);
return {
name: safeName,
type: this.mapTypeScriptToSolidity(type)
};
}
else {
return {
name: this.getSafeParameterName(param || `param${index + 1}`),
type: 'bytes'
};
}
});
}
}
return [];
}
getSafeParameterName(name) {
// Solidity reserved keywords that we need to avoid
const reservedKeywords = [
'address', 'bool', 'string', 'bytes', 'uint256', 'int256',
'function', 'contract', 'library', 'interface', 'struct', 'enum',
'mapping', 'array', 'modifier', 'event', 'using', 'import',
'pragma', 'assembly', 'memory', 'storage', 'calldata', 'pure', 'view',
'payable', 'nonpayable', 'external', 'internal', 'public', 'private'
];
if (reservedKeywords.includes(name.toLowerCase())) {
return `${name}_param`;
}
return name;
}
addDataLocationForInternal(type) {
if (type === 'string' || type === 'bytes' || type.endsWith('[]')) {
return `${type} memory`;
}
return type;
}
addDataLocation(type) {
// Add appropriate data location for reference types in external functions
if (type === 'string' || type === 'bytes' || type.endsWith('[]')) {
return `${type} calldata`;
}
return type;
}
addDataLocationForAbiDecode(type) {
// For abi.decode results, reference types should use memory
if (type === 'string' || type === 'bytes' || type.endsWith('[]')) {
return `${type} memory`;
}
return type;
}
generateSolidityLibrary(functions, packageJson) {
const contractsDir = path.join(this.targetDir, 'contracts');
if (!fs.existsSync(contractsDir)) {
fs.mkdirSync(contractsDir, { recursive: true });
}
// Generate combined NavsReceiver abstract contract (includes service functions)
const combinedSolidity = this.generateCombinedNavsReceiver(functions, packageJson);
const receiverPath = path.join(contractsDir, 'NavsReceiver.sol');
fs.writeFileSync(receiverPath, combinedSolidity);
console.log('✓ Generated contracts/NavsReceiver.sol');
}
generateServiceLibrary(functions, packageJson) {
const libraryName = this.generateLibraryName(packageJson.name);
let solidityCode = `// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "navs/src/ITaskDispatch.sol";
import "navs/src/IBaseNavsReceiver.sol";
/**
* Auto-generated Service Library for NAVS function calls
*
* This library provides asynchronous calls for functions decorated with navs annotations.
* All functions submit tasks via TaskDispatch and receive results via callbacks.
*
* Generated by navs-gen on ${new Date().toISOString()}
* Total functions: ${functions.length}
*/
library ${libraryName} {
// Package information constants
string internal constant PACKAGE_NAME = "${packageJson.name || 'unknown'}";
string internal constant PACKAGE_VERSION = "${packageJson.version || '1.0.0'}";
${functions.length === 0 ? ' // No navs functions found in the project' : ''}`;
// Generate async function implementations
for (const func of functions) {
solidityCode += this.generateAsyncFunction(func);
}
// Add utility functions
solidityCode += this.generateUtilityFunctions(packageJson);
solidityCode += '}\n';
return solidityCode;
}
generateCombinedNavsReceiver(functions, packageJson) {
// Get TaskDispatch address for modifier
const taskDispatchAddresses = this.getTaskDispatchAddresses();
const localWarning = this.isLocal ? `
/*
╔══════════════════════════════════════════════════════════════════════════════════════╗
║ ⚠️ LOCAL MODE ⚠️ ║
║ ║
║ This contract was generated with --local flag for LOCAL DEVELOPMENT ONLY! ║
║ ║
║ 🏠 LOCAL FEATURES ENABLED: ║
║ • Zero stake requirement (no economic cost) ║
║ • Immediate task completion (no consensus waiting) ║
║ • Uses file:// dependencies instead of npm packages ║
║ • Only processes tasks matching current package directory ║
║ ║
║ 🚨 DO NOT USE IN PRODUCTION! This is for testing and development only. ║
║ For production, regenerate without --local flag. ║
╚══════════════════════════════════════════════════════════════════════════════════════╝
*/
` : '';
let combinedCode = `// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "navs/src/ITaskDispatch.sol";
import "navs/src/IBaseNavsReceiver.sol";
${localWarning}
/**
* Abstract NavsReceiver contract that implements IBaseNavsReceiver
*
* This contract combines service functions and callback handling in one abstract contract.
* Users should extend this contract and override the abstract callback methods they need.
* This contract automatically handles onNavsResult and onNavsError routing from TaskDispatch.
*
* Generated by navs-gen on ${new Date().toISOString()}
* Total functions: ${functions.length}${this.isLocal ? '\n * \n * 🏠 LOCAL MODE: Generated with --local flag for development only!' : ''}
*/
abstract contract NavsReceiver is IBaseNavsReceiver {
// Package information constants
string public constant PACKAGE_NAME = "${packageJson.name || 'unknown'}";
string public constant PACKAGE_VERSION = "${packageJson.version || '1.0.0'}";
${taskDispatchAddresses}
/**
* Modifier to ensure only TaskDispatch can call callback functions
*/
modifier onlyTaskDispatch() {
address taskDispatchAddr = getTaskDispatchAddress();
require(msg.sender == taskDispatchAddr, "Only TaskDispatch can call this function");
_;
}
/**
* Implementation of IBaseNavsReceiver.onNavsResult
* Automatically routes to typed callback methods
* Can only be called by TaskDispatch contract
*/
function onNavsResult(uint256 taskId, string memory functionName, bytes memory result) external override onlyTaskDispatch {
_handleNavsCallback(taskId, functionName, result, "");
}
/**
* Implementation of IBaseNavsReceiver.onNavsError
* Automatically routes to typed callback methods with error message
* Can only be called by TaskDispatch contract
*/
function onNavsError(uint256 taskId, string memory functionName, string memory error) external override onlyTaskDispatch {
_handleNavsErrorCallback(taskId, functionName, error);
}
/**
* Internal callback router for type-safe callbacks
*/
function _handleNavsCallback(
uint256 taskId,
string memory functionName,
bytes memory result,
string memory error
) internal {
${this.generateInternalCallbackRouter(functions)}
revert("Unknown function");
}
/**
* Internal error callback router for type-safe error callbacks
*/
function _handleNavsErrorCallback(
uint256 taskId,
string memory functionName,
string memory error
) internal {
${this.generateInternalErrorCallbackRouter(functions)}
revert("Unknown function");
}
// ================== SERVICE FUNCTIONS ==================${this.isLocal ? `
/*
* 🏠 LOCAL MODE SERVICE FUNCTIONS
*
* These functions are configured for local development:
* • requiredStakeWei parameter is ignored (always uses 0 stake)
* • Tasks complete immediately without consensus
* • Only operators monitoring this specific package will process tasks
*/` : ''}
${functions.length === 0 ? ' // No navs functions found in the project' : ''}`;
// Generate async function implementations
for (const func of functions) {
combinedCode += this.generateAsyncFunction(func);
}
// Add utility functions (without getTaskDispatchAddress since it's already included above)
combinedCode += this.generateUtilityFunctionsForContract(packageJson);
// Generate abstract callback methods
combinedCode += '\n // ================== ABSTRACT CALLBACK METHODS ==================\n';
combinedCode += functions.map(func => this.generateAbstractCallbackMethod(func)).join('');
combinedCode += '}\n';
return combinedCode;
}
generateNavsReceiverAbstract(functions, libraryName, packageJson) {
// Get TaskDispatch address for modifier
const taskDispatchAddresses = this.getTaskDispatchAddresses();
let abstractCode = `// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "navs/src/IBaseNavsReceiver.sol";
import "./service.sol";
/**
* Abstract NavsReceiver contract that implements IBaseNavsReceiver
*
* Users should extend this contract and override the abstract callback methods they need.
* This contract automatically handles onNavsResult and onNavsError routing from TaskDispatch.
*
* Generated by navs-gen on ${new Date().toISOString()}
*/
abstract contract NavsReceiver is IBaseNavsReceiver {
using ${libraryName} for *;
// Package information constants
string public constant PACKAGE_NAME = "${packageJson.name || 'unknown'}";
string public constant PACKAGE_VERSION = "${packageJson.version || '1.0.0'}";
${taskDispatchAddresses}
/**
* Modifier to ensure only TaskDispatch can call callback functions
*/
modifier onlyTaskDispatch() {
address taskDispatchAddr = getTaskDispatchAddress();
require(msg.sender == taskDispatchAddr, "Only TaskDispatch can call this function");
_;
}
/**
* Implementation of IBaseNavsReceiver.onNavsResult
* Automatically routes to typed callback methods
* Can only be called by TaskDispatch contract
*/
function onNavsResult(uint256 taskId, string memory functionName, bytes memory result) external override onlyTaskDispatch {
_handleNavsCallback(taskId, functionName, result, "");
}
/**
* Implementation of IBaseNavsReceiver.onNavsError
* Automatically routes to typed callback methods with error message
* Can only be called by TaskDispatch contract
*/
function onNavsError(uint256 taskId, string memory functionName, string memory error) external override onlyTaskDispatch {
_handleNavsErrorCallback(taskId, functionName, error);
}
/**
* Internal callback router for type-safe callbacks
*/
function _handleNavsCallback(
uint256 taskId,
string memory functionName,
bytes memory result,
string memory error
) internal {
${this.generateInternalCallbackRouter(functions)}
revert("Unknown function");
}
/**
* Internal error callback router for type-safe error callbacks
*/
function _handleNavsErrorCallback(
uint256 taskId,
string memory functionName,
string memory error
) internal {
${this.generateInternalErrorCallbackRouter(functions)}
revert("Unknown function");
}
// Abstract callback methods - override these in your contract
${functions.map(func => this.generateAbstractCallbackMethod(func)).join('')}
}
`;
return abstractCode;
}
generateAsyncFunction(func) {
const params = func.parameters.map(p => `${this.addDataLocation(p.type)} ${p.name}`).join(', ');
const paramNames = func.parameters.map(p => p.name);
const hasParams = paramNames.length > 0;
const allParams = hasParams
? `${params}, uint256 requiredStakeWei`
: 'uint256 requiredStakeWei';
const paramDocs = func.parameters.length > 0
? func.parameters.map(p => ` * ${p.name} Parameter of type ${p.type}`).join('\n')
: ' * No parameters';
const localComment = this.isLocal ? '\n * 🏠 LOCAL MODE: requiredStakeWei is ignored, always uses 0 stake for local development' : '';
return `
/**
* Call ${func.name} with callback
* ${func.description || `Calls ${func.name} function via TaskDispatch and returns result via callback`}
* The calling contract (address(this)) will receive the callback.
${paramDocs}
* @param requiredStakeWei Minimum stake threshold for execution in wei${localComment}
* @return taskId Unique identifier for tracking this task
*/
function ${func.name}(${allParams}) internal returns (uint256 taskId) {
${hasParams
? `bytes memory encodedParams = abi.encode(${paramNames.join(', ')});`
: `bytes memory encodedParams = "";`}
return _submitTaskWithCallback(
"${func.name}",
encodedParams,
address(this),
${this.isLocal ? '0, // no stake used for local development' : 'requiredStakeWei'},
ITaskDispatch.ConsensusType.${func.consensusType},
${this.isLocal}
);
}
`;
}
generateAbstractCallbackMethod(func) {
const params = func.parameters.map(p => `${this.addDataLocationForInternal(p.type)} ${p.name}`).join(', ');
const resultType = this.addDataLocationForInternal(func.returnType);
const allParams = params ? `uint256 taskId, ${params}, ${resultType} result, string memory error` : `uint256 taskId, ${resultType} result, string memory error`;
const paramDocs = func.parameters.length > 0
? func.parameters.map(p => ` * ${p.name} Original parameter of type ${p.type}`).join('\n')
: '';
return `
/**
* Abstract callback for ${func.name} function completion
* Override this function in your contract to handle ${func.name} results
* @param taskId The unique identifier for the completed task
${paramDocs}
* @param result The result returned by ${func.name}
* @param error Error message if the function failed, empty string if successful
*/
function on${func.name.charAt(0).toUpperCase() + func.name.slice(1)}(${allParams}) internal virtual;
`;
}
generateInternalCallbackRouter(functions) {
let router = '';
for (const func of functions) {
const paramNames = func.parameters.map(p => p.name);
const hasParams = paramNames.length > 0;
const capitalizedName = func.name.charAt(0).toUpperCase() + func.name.slice(1);
router += ` if (keccak256(bytes(functionName)) == keccak256(bytes("${func.name}"))) {\n`;
router += ` // Decode the enhanced callback data from TaskDispatch\n`;
router += ` (bytes memory originalArgs, bytes memory packedResult) = abi.decode(result, (bytes, bytes));\n`;
router += ` \n`;
router += ` // Decode the packed result from navs-core\n`;
router += ` (string memory resultType, bytes memory resultBytes, bytes32 l1calldatahash) = abi.decode(packedResult, (string, bytes, bytes32));\n`;
router += ` \n`;
if (hasParams) {
// For functions with parameters, decode both original params and function result
const paramTypes = func.parameters.map(p => p.type);
const paramVarDeclarations = paramTypes.map((type, i) => `${this.addDataLocationForAbiDecode(type)} param${i}`);
const paramVarNames = paramTypes.map((_, i) => `param${i}`);
router += ` // Decode the original parameters\n`;
router += ` (${paramVarDeclarations.join(', ')}) = abi.decode(originalArgs, (${paramTypes.join(', ')}));\n`;
router += ` \n`;
router += ` // Decode the function result\n`;
router += ` ${this.addDataLocationForAbiDecode(func.returnType)} resultValue = abi.decode(resultBytes, (${func.returnType}));\n`;
router += ` \n`;
router += ` on${capitalizedName}(taskId, ${paramVarNames.join(', ')}, resultValue, error);\n`;
}
else {
// For functions without parameters, just decode the function result
router += ` // Decode the function result\n`;
router += ` ${this.addDataLocationForAbiDecode(func.returnType)} resultValue = abi.decode(resultBytes, (${func.returnType}));\n`;
router += ` \n`;
router += ` on${capitalizedName}(taskId, resultValue, error);\n`;
}
router += ` return;\n`;
router += ` }\n`;
}
return router;
}
generateInternalErrorCallbackRouter(functions) {
let router = '';
for (const func of functions) {
const capitalizedName = func.name.charAt(0).toUpperCase() + func.name.slice(1);
const hasParams = func.parameters.length > 0;
router += ` if (keccak256(bytes(functionName)) == keccak256(bytes("${func.name}"))) {\n`;
if (hasParams) {
const defaultParams = func.parameters.map(p => {
return this.getDefaultValue(p.type);
}).join(', ');
const defaultReturnValue = this.getDefaultValue(func.returnType);
router += ` on${capitalizedName}(taskId, ${defaultParams}, ${defaultReturnValue}, error);\n`;
}
else {
const defaultReturnValue = this.getDefaultValue(func.returnType);
router += ` on${capitalizedName}(taskId, ${defaultReturnValue}, error);\n`;
}
router += ` return;\n`;
router += ` }\n`;
}
return router;
}
getDefaultValue(type) {
// Handle array types first (before checking for uint/int)
if (type.endsWith('[]')) {
return `new ${type}(0)`;
}
if (type === 'string')
return '""';
if (type === 'bytes')
return 'bytes("")';
if (type.includes('uint') || type.includes('int'))
return '0';
if (type === 'bool')
return 'false';
if (type === 'address')
return 'address(0)';
return 'bytes("")'; // fallback for complex types
}
generateUtilityFunctionsForContract(packageJson) {
return `
/**
* Internal function to handle asynchronous task submission with callback
* @param functionName The name of the function to execute
* @param encodedParams ABI-encoded parameters
* @param callbackReceiver Address that will receive the callback
* @param requiredStake Minimum stake threshold for execution
* @param consensusType Type of consensus to use for the task
* @param isLocal Whether this is a local task
* @return taskId Unique identifier for tracking this task
*/
function _submitTaskWithCallback(
string memory functionName,
bytes memory encodedParams,
address callbackReceiver,
uint256 requiredStake,
ITaskDispatch.ConsensusType consensusType,
bool isLocal
) internal returns (uint256) {
// Call TaskDispatch with callback receiver
address taskDispatchAddr = getTaskDispatchAddress();
require(taskDispatchAddr != address(0), "TaskDispatch address not set for this chain");
uint256 taskId = ITaskDispatch(taskDispatchAddr).submitTask(
"${packageJson.name || 'unknown-service'}", // Service name from package.json
"${packageJson.version || '1.0.0'}", // Service version from package.json
functionName,
encodedParams,
requiredStake, // Use the provided stake threshold
consensusType, // Use the determined consensus type
false, // isConsensus
callbackReceiver, // Callback will be executed automatically by TaskDispatch
isLocal, // Whether this is a local task
true // isDeterministic - auto-mark generated tasks as deterministic
);
return taskId;
}`;
}
generateUtilityFunctions(packageJson) {
// Get TaskDispatch addresses from navs-deploy
const taskDispatchAddresses = this.getTaskDispatchAddresses();
return `
// TaskDispatch contract addresses from navs-deploy
${taskDispatchAddresses}
/**
* Internal function to handle asynchronous task submission with callback
* @param functionName The name of the function to execute
* @param encodedParams ABI-encoded parameters
* @param callbackReceiver Address that will receive the callback
* @param requiredStake Minimum stake threshold for execution
* @param consensusType Type of consensus to use for the task
* @param isLocal Whether this is a local task
* @return taskId Unique identifier for tracking this task
*/
function _submitTaskWithCallback(
string memory functionName,
bytes memory encodedParams,
address callbackReceiver,
uint256 requiredStake,
ITaskDispatch.ConsensusType consensusType,
bool isLocal
) internal returns (uint256) {
// Call TaskDispatch with callback receiver
address taskDispatchAddr = getTaskDispatchAddress();
require(taskDispatchAddr != address(0), "TaskDispatch address not set for this chain");
uint256 taskId = ITaskDispatch(taskDispatchAddr).submitTask(
"${packageJson.name || 'unknown-service'}", // Service name from package.json
"${packageJson.version || '1.0.0'}", // Service version from package.json
functionName,
encodedParams,
requiredStake, // Use the provided stake threshold
consensusType, // Use the determined consensus type
false, // isConsensus
callbackReceiver, // Callback will be executed automatically by TaskDispatch
isLocal, // Whether this is a local task
true // isDeterministic - auto-mark generated tasks as deterministic
);
return taskId;
}
`;
}
generateLibraryName(packageName) {
// Remove invalid characters and convert to PascalCase
const cleaned = packageName
.replace(/[@/\-_]/g, ' ') // Replace special chars with spaces
.replace(/[^a-zA-Z0-9\s]/g, '') // Remove any remaining invalid chars
.split(' ')
.filter(word => word.length > 0)
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join('');
// Ensure it starts with a letter and is a valid Solidity identifier
const result = cleaned.replace(/^[0-9]/, 'N$&'); // Prefix with 'N' if starts with number
return result || 'NavsService'; // Fallback if empty
}
mapTypeScriptToSolidity(tsType) {
// Handle Promise types
if (tsType.startsWith('Promise<') && tsType.endsWith('>')) {
const innerType = tsType.slice(8, -1);
return this.mapTypeScriptToSolidity(innerType);
}
// Handle Ethereum address template literal types
if (tsType === '`0x${string}`' || tsType === '0x${string}') {
return 'address';
}
// Basic type mappings
const typeMap = {
'string': 'string',
'String': 'string',
'number': 'uint256',
'Number': 'uint256',
'boolean': 'bool',
'Boolean': 'bool',
'bigint': 'uint256',
'BigInt': 'uint256',
'undefined': 'bytes',
'unknown': 'bytes',
'any': 'bytes',
'void': 'bytes',
'object': 'bytes',
'Object': 'bytes'
};
// Handle array types
if (tsType.endsWith('[]')) {
const elementType = tsType.slice(0, -2);
const mappedElementType = this.mapTypeScriptToSolidity(elementType);
return `${mappedElementType}[]`;
}
// Handle Array<T> syntax
if (tsType.startsWith('Array<') && tsType.endsWith('>')) {
const elementType = tsType.slice(6, -1);
const mappedElementType = this.mapTypeScriptToSolidity(elementType);
return `${mappedElementType}[]`;
}
return typeMap[tsType] || 'bytes';
}
getTaskDispatchAddresses() {
let addressesCode = '';
// Generate getter function for TaskDispatch address
addressesCode += `
/**
* Get TaskDispatch contract address for current chain
* @return address TaskDispatch contract address (address(0) if not deployed on this chain)
*/
function getTaskDispatchAddress() internal view virtual returns (address) {
uint256 chainId = block.chainid;`;
// Add addresses from navs-deploy
for (const [chainId, contracts] of Object.entries(deploy_1.Addresses)) {
const contractsAny = contracts;
if (contractsAny.TaskDispatch) {
addressesCode += `
if (chainId == ${chainId}) return ${(0, viem_1.getAddress)(contractsAny.TaskDispatch)}; // ${this.getChainName(chainId)}`;
}
}
addressesCode += `
return address(0); // TaskDispatch not deployed on this chain
}`;
return addressesCode;
}
getChainName(chainId) {
const chainNames = {
'1': 'Ethereum Mainnet',
'11155111': 'Ethereum Sepolia',
'84532': 'Base Sepolia',
'8453': 'Base Mainnet'
};
return chainNames[chainId] || `Chain ${chainId}`;
}
}
exports.NavsGenerator = NavsGenerator;