UNPKG

@factorial-finance/blueprint-node

Version:

blueprint-node-plugin

309 lines (308 loc) 14.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.JSONRPCHandlers = void 0; const json_rpc_2_0_1 = require("json-rpc-2.0"); const core_1 = require("@ton/core"); const logger_1 = require("../utils/logger"); const test_utils_1 = require("@ton/test-utils"); const defaults_1 = require("../constants/defaults"); const transactionLogger_1 = require("../utils/transactionLogger"); const utils_1 = require("../utils"); class JSONRPCHandlers { constructor(blockchainService, aliasService) { this.getMasterchainInfo = this.createUnimplementedHandler('getMasterchainInfo'); this.getShards = this.createUnimplementedHandler('getShards'); this.estimateFee = this.createUnimplementedHandler('estimateFee'); this.getBlockTransactions = this.createUnimplementedHandler('getBlockTransactions'); this.server = new json_rpc_2_0_1.JSONRPCServer(); this.blockchainService = blockchainService; this.aliasService = aliasService; this.setupHandlers(); } // Common error handler for contract operations async handleContractOperation(operation, operationName, errorMessage) { try { return await operation(); } catch (error) { logger_1.Logger.logError(operationName, error); if (error instanceof utils_1.ContractError) { throw { code: json_rpc_2_0_1.JSONRPCErrorCode.InternalError, message: error.message }; } const message = error instanceof Error ? error.message : errorMessage; throw { code: json_rpc_2_0_1.JSONRPCErrorCode.InternalError, message }; } } // Handler for unimplemented methods createUnimplementedHandler(methodName) { return () => { const error = { code: json_rpc_2_0_1.JSONRPCErrorCode.MethodNotFound, message: `Method ${methodName} is not implemented` }; logger_1.Logger.logError(methodName, error); throw error; }; } setupHandlers() { this.server.addMethod('sendBoc', this.sendBoc.bind(this)); this.server.addMethod('runGetMethod', this.runGetMethod.bind(this)); this.server.addMethod('getAddressInformation', this.getAddressInformation.bind(this)); this.server.addMethod('getTransactions', this.getTransactions.bind(this)); this.server.addMethod('getTransaction', this.getTransaction.bind(this)); this.server.addMethod('tryLocateResultTx', this.tryLocateResultTx.bind(this)); this.server.addMethod('tryLocateSourceTx', this.tryLocateSourceTx.bind(this)); this.server.addMethod('getMasterchainInfo', this.getMasterchainInfo.bind(this)); this.server.addMethod('getShards', this.getShards.bind(this)); this.server.addMethod('estimateFee', this.estimateFee.bind(this)); this.server.addMethod('getBlockTransactions', this.getBlockTransactions.bind(this)); // custom methods this.server.addMethod('setBalance', this.setBalance.bind(this)); this.server.addMethod('increaseBalance', this.increaseBalance.bind(this)); this.server.addMethod('setAccountCode', this.setAccountCode.bind(this)); this.server.addMethod('setAccountData', this.setAccountData.bind(this)); this.server.addMethod('addLibrary', this.addLibrary.bind(this)); this.server.addMethod('setLibraries', this.setLibraries.bind(this)); this.server.addMethod('getLibraries', this.getLibraries.bind(this)); } async setBalance(params) { logger_1.Logger.logRPCCall('setBalance', params); (0, utils_1.validateAddress)(params.address); await this.blockchainService.setBalance(core_1.Address.parse(params.address), BigInt(params.balance)); return { '@type': 'ok' }; } async increaseBalance(params) { (0, utils_1.validateAddress)(params.address); logger_1.Logger.logRPCCall('increaseBalance', params); await this.blockchainService.increaseBalance(core_1.Address.parse(params.address), BigInt(params.amount)); return { '@type': 'ok' }; } async setAccountCode(params) { return this.handleContractOperation(async () => { const address = core_1.Address.parse(params.address); const code = core_1.Cell.fromBase64(params.code); // Create internal params with codeHash const internalParams = { ...params, codeHash: code.hash().toString('hex') }; logger_1.Logger.logRPCCall('setAccountCode', internalParams); await this.blockchainService.setAccountCode(address, code); return { '@type': 'ok' }; }, 'setAccountCode', 'Failed to set account code'); } async setAccountData(params) { return this.handleContractOperation(async () => { const address = core_1.Address.parse(params.address); const data = core_1.Cell.fromBase64(params.data); // Create internal params with dataHash const internalParams = { ...params, dataHash: data.hash().toString('hex') }; logger_1.Logger.logRPCCall('setAccountData', internalParams); await this.blockchainService.setAccountData(address, data); return { '@type': 'ok' }; }, 'setAccountData', 'Failed to set account data'); } async addLibrary(params) { logger_1.Logger.logRPCCall('addLibrary', params); const library = core_1.Cell.fromBase64(params.library); await this.blockchainService.addLibrary(library); return { '@type': 'ok' }; } async setLibraries(params) { logger_1.Logger.logRPCCall('setLibraries', params); const libraries = core_1.Cell.fromBase64(params.libraries); await this.blockchainService.setLibraries(libraries); return { '@type': 'ok' }; } async getLibraries() { logger_1.Logger.logRPCCall('getLibraries', {}); const libraries = await this.blockchainService.getLibraries(); return { cell: libraries.size > 0 ? (0, core_1.beginCell)().storeDictDirect(libraries).endCell().toBoc().toString('base64') : null }; } async getAddressInformation(params) { logger_1.Logger.logRPCCall('getAddressInformation', params); try { (0, utils_1.validateAddress)(params.address); const addr = core_1.Address.parse(params.address); const blockchain = this.blockchainService.getBlockchain(); const contractState = await blockchain.provider(addr).getState(); const response = { balance: contractState.balance.toString(), state: contractState.state.type === 'uninit' ? 'uninitialized' : contractState.state.type, data: '', code: '', last_transaction_id: { '@type': 'internal.transactionId', lt: contractState.last ? contractState.last.lt.toString() : '0', hash: contractState.last ? contractState.last.hash.toString('base64') : '' }, block_id: { '@type': 'ton.blockIdExt', workchain: addr.workChain, shard: '', seqno: 0, root_hash: '', file_hash: '' }, sync_utime: this.blockchainService.getCurrentTime() }; if (contractState.state.type === 'active') { if (contractState.state.code) { response.code = core_1.Cell.fromBoc(contractState.state.code)[0].toBoc().toString('base64'); } if (contractState.state.data) { response.data = core_1.Cell.fromBoc(contractState.state.data)[0].toBoc().toString('base64'); } } if (contractState.state.type === 'frozen') { response.frozen_hash = contractState.state.stateHash.toString('base64'); } return response; } catch (error) { logger_1.Logger.logError('getAddressInformation', error); if (error instanceof utils_1.ValidationError) { throw { code: error.code, message: error.message }; } throw { code: json_rpc_2_0_1.JSONRPCErrorCode.InvalidParams, message: 'Invalid address format' }; } } async runGetMethod(params) { logger_1.Logger.logRPCCall('runGetMethod', params); try { (0, utils_1.validateAddress)(params.address); (0, utils_1.validateMethod)(params.method); const addr = core_1.Address.parse(params.address); const blockchain = this.blockchainService.getBlockchain(); const stackItems = (0, utils_1.parseStack)(params.stack); const result = await blockchain.runGetMethod(addr, params.method, stackItems); return { gas_used: Number(result.gasUsed), exit_code: result.exitCode, stack: (0, utils_1.serializeStack)(Array.from(result.stack)) }; } catch (error) { logger_1.Logger.logError('runGetMethod', error); return { gas_used: 0, exit_code: -1, stack: [] }; } } async getTransactions(params) { logger_1.Logger.logRPCCall('getTransactions', params); try { (0, utils_1.validateAddress)(params.address); const txm = this.blockchainService.getTransactions(params.address); return txm .sort((a, b) => a.lt < b.lt ? 1 : -1) .filter((tx) => tx.lt >= BigInt(params.lt || "0")) .slice(0, params.limit || defaults_1.DEFAULTS.TRANSACTION_LIMIT) .map(utils_1.convertTransaction); } catch (error) { logger_1.Logger.logError('getTransactions', error); if (error instanceof utils_1.ValidationError || error instanceof utils_1.ContractError) { throw { code: error.code, message: error.message }; } throw { code: json_rpc_2_0_1.JSONRPCErrorCode.InvalidParams, message: 'Invalid parameters' }; } } async sendBoc(params) { logger_1.Logger.logRPCCall('sendBoc', params); try { (0, utils_1.validateBoc)(params.boc); const transactions = await this.blockchainService.sendBoc(params.boc); const flattenTxs = transactions.map(test_utils_1.flattenTransaction); // Log the transaction tree await (0, transactionLogger_1.logFlattenTransactions)(flattenTxs, this.aliasService, this.blockchainService); return { '@type': 'ok' }; } catch (error) { logger_1.Logger.logError('sendBoc', error); if (error instanceof utils_1.ValidationError) { throw { code: error.code, message: error.message }; } throw { code: json_rpc_2_0_1.JSONRPCErrorCode.InvalidParams, message: 'Invalid BOC' }; } } getTransaction(params) { logger_1.Logger.logRPCCall('getTransaction', params); try { (0, utils_1.validateAddress)(params.address); const txm = this.blockchainService.getTransactions(params.address); const tx = txm.find((tx) => tx.lt.toString() === params.lt && tx.hash.toString('base64') === params.hash); if (!tx) { throw { code: json_rpc_2_0_1.JSONRPCErrorCode.InvalidParams, message: 'Transaction not found' }; } return (0, utils_1.convertTransaction)(tx); } catch (error) { logger_1.Logger.logError('getTransaction', error); if (error instanceof utils_1.ValidationError) { throw { code: error.code, message: error.message }; } throw { code: json_rpc_2_0_1.JSONRPCErrorCode.InvalidParams, message: 'Invalid parameters' }; } } tryLocateResultTx(params) { logger_1.Logger.logRPCCall('tryLocateResultTx', params); try { (0, utils_1.validateAddress)(params.destination); const txm = this.blockchainService.getTransactions(params.destination); const tx = txm.find((tx) => { return tx.inMessage && tx.inMessage.source && tx.inMessage.source.toString() === params.source && tx.inMessage.createdLt && tx.inMessage.createdLt.toString() === params.created_lt; }); if (!tx) { throw { code: json_rpc_2_0_1.JSONRPCErrorCode.InvalidParams, message: 'Transaction not found' }; } return (0, utils_1.convertTransaction)(tx); } catch (error) { logger_1.Logger.logError('tryLocateResultTx', error); if (error instanceof utils_1.ValidationError) { throw { code: error.code, message: error.message }; } throw { code: json_rpc_2_0_1.JSONRPCErrorCode.InvalidParams, message: 'Invalid parameters' }; } } tryLocateSourceTx(params) { logger_1.Logger.logRPCCall('tryLocateSourceTx', params); try { (0, utils_1.validateAddress)(params.source); const txm = this.blockchainService.getTransactions(params.source); const tx = txm.find((tx) => { return tx.outMessages && tx.outMessages.some((msg) => msg.destination && msg.destination.toString() === params.destination && msg.createdLt && msg.createdLt.toString() === params.created_lt); }); if (!tx) { throw { code: json_rpc_2_0_1.JSONRPCErrorCode.InvalidParams, message: 'Transaction not found' }; } return (0, utils_1.convertTransaction)(tx); } catch (error) { logger_1.Logger.logError('tryLocateSourceTx', error); if (error instanceof utils_1.ValidationError) { throw { code: error.code, message: error.message }; } throw { code: json_rpc_2_0_1.JSONRPCErrorCode.InvalidParams, message: 'Invalid parameters' }; } } getServer() { return this.server; } } exports.JSONRPCHandlers = JSONRPCHandlers;