@factorial-finance/blueprint-node
Version:
blueprint-node-plugin
309 lines (308 loc) • 14.7 kB
JavaScript
"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;