UNPKG

sails-js

Version:

Typescript library for working with Sails programs

277 lines (274 loc) 11.9 kB
var api = require('@gear-js/api'); var create = require('@polkadot/types/create'); var util = require('@polkadot/util'); var prefix = require('./prefix.cjs'); var transactionBuilder = require('./transaction-builder.cjs'); require('./util/lib/payload-method.cjs'); var types = require('./util/lib/types.cjs'); var consts = require('./consts.cjs'); var utils = require('./utils.cjs'); class Sails { _parser; _program; _scaleTypes; _registry; _api; _programId; constructor(parser) { this._parser = parser; } /** ### Set api to use for transactions */ setApi(api) { this._api = api; return this; } /** ### Set program id to interact with */ setProgramId(programId) { this._programId = programId; return this; } /** ### Get program id */ get programId() { return this._programId; } /** * ### Parse IDL from string * @param idl - IDL string */ parseIdl(idl) { if (!this._parser) { throw new Error('Parser not set. Use sails-js-parser package to initialize the parser and pass it to the Sails constructor.'); } this._program = this._parser.parse(idl); this.generateScaleCodeTypes(); return this; } generateScaleCodeTypes() { const scaleTypes = {}; for (const type of this._program.types) { scaleTypes[type.name] = types.getScaleCodecDef(type.def); } this._registry = new create.TypeRegistry(); this._registry.setKnownTypes({ types: scaleTypes }); this._registry.register(scaleTypes); this._scaleTypes = scaleTypes; } /** #### Scale code types from the parsed IDL */ get scaleCodecTypes() { if (!this._program) { throw new Error('IDL not parsed'); } return this._scaleTypes; } /** #### Registry with registered types from the parsed IDL */ get registry() { if (!this._program) { throw new Error('IDL not parsed'); } return this._registry; } _getFunctions(service) { const funcs = {}; const queries = {}; for (const func of service.funcs) { const params = func.params.map((p) => ({ name: p.name, type: types.getScaleCodecDef(p.def), typeDef: p.def })); const returnType = types.getScaleCodecDef(func.def); if (func.isQuery) { queries[func.name] = (async (origin = consts.ZERO_ADDRESS, value = 0n, atBlock, ...args) => { if (!this._api) { throw new Error('API is not set. Use .setApi method to set API instance'); } if (!this._programId) { throw new Error('Program ID is not set. Use .setProgramId method to set program ID'); } const payload = this.registry .createType(`(String, String, ${params.map((p) => p.type).join(', ')})`, [service.name, func.name, ...args]) .toHex(); const reply = await this._api.message.calculateReply({ destination: this.programId, origin: api.decodeAddress(origin), payload, value, gasLimit: this._api.blockGasLimit.toBigInt(), at: atBlock || null, }); utils.throwOnErrorReply(reply.code, reply.payload.toU8a(), this._api.specVersion, this._registry); const result = this.registry.createType(`(String, String, ${returnType})`, reply.payload.toHex()); return result[2].toJSON(); }); } else { funcs[func.name] = ((...args) => { if (!this._api) { throw new Error('API is not set. Use .setApi method to set API instance'); } if (!this._programId) { throw new Error('Program ID is not set. Use .setProgramId method to set program ID'); } return new transactionBuilder.TransactionBuilder(this._api, this.registry, 'send_message', service.name, func.name, [...args], `(${params.map((p) => p.type).join(', ')})`, returnType, this._programId); }); } Object.assign(func.isQuery ? queries[func.name] : funcs[func.name], { args: params, returnType, returnTypeDef: func.def, docs: func.docs, encodePayload: (...args) => { if (args.length !== args.length) { throw new Error(`Expected ${params.length} arguments, but got ${args.length}`); } const payload = this.registry.createType(`(String, String, ${params.map((p) => p.type).join(', ')})`, [ service.name, func.name, ...args, ]); return payload.toHex(); }, decodePayload: (bytes) => { const payload = this.registry.createType(`(String, String, ${params.map((p) => p.type).join(', ')})`, bytes); const result = {}; for (const [i, param] of params.entries()) { result[param.name] = payload[i + 2].toJSON(); } return result; }, decodeResult: (result) => { const payload = this.registry.createType(`(String, String, ${returnType})`, result); return payload[2].toJSON(); }, }); } return { funcs, queries }; } _getEvents(service) { const events = {}; for (const event of service.events) { const t = event.def ? types.getScaleCodecDef(event.def) : 'Null'; const typeStr = event.def ? types.getScaleCodecDef(event.def, true) : 'Null'; events[event.name] = { type: t, typeDef: event.def, docs: event.docs, is: ({ data: { message } }) => { if (!message.destination.eq(consts.ZERO_ADDRESS)) { return false; } if (prefix.getServiceNamePrefix(message.payload.toHex()) !== service.name) { return false; } if (prefix.getFnNamePrefix(message.payload.toHex()) !== event.name) { return false; } return true; }, decode: (payload) => { const data = this.registry.createType(`(String, String, ${typeStr})`, payload); return data[2].toJSON(); }, subscribe: (cb) => { if (!this._api) { throw new Error('API is not set. Use .setApi method to set API instance'); } if (!this._programId) { throw new Error('Program ID is not set. Use .setProgramId method to set program ID'); } return this._api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => { if (!message.source.eq(this._programId)) return; if (!message.destination.eq(consts.ZERO_ADDRESS)) return; const payload = message.payload.toHex(); if (prefix.getServiceNamePrefix(payload) === service.name && prefix.getFnNamePrefix(payload) === event.name) { cb(this.registry.createType(`(String, String, ${typeStr})`, message.payload)[2].toJSON()); } }); }, }; } return events; } /** #### Services with functions and events from the parsed IDL */ get services() { if (!this._program) { throw new Error('IDL is not parsed'); } const services = {}; for (const service of this._program.services) { const { funcs, queries } = this._getFunctions(service); services[service.name] = { functions: funcs, queries, events: this._getEvents(service), }; } return services; } /** #### Constructor functions with arguments from the parsed IDL */ get ctors() { if (!this._program) { throw new Error('IDL not parsed'); } const ctor = this._program.ctor; if (!ctor) { return null; } const funcs = {}; for (const func of ctor.funcs) { const params = func.params.map((p) => ({ name: p.name, type: types.getScaleCodecDef(p.def), typeDef: p.def })); funcs[func.name] = { args: params, encodePayload: (...args) => { if (args.length !== args.length) { throw new Error(`Expected ${params.length} arguments, but got ${args.length}`); } if (params.length === 0) { return util.u8aToHex(this.registry.createType('String', func.name).toU8a()); } const payload = this.registry.createType(`(String, ${params.map((p) => p.type).join(', ')})`, [ func.name, ...args, ]); return payload.toHex(); }, decodePayload: (bytes) => { const payload = this.registry.createType(`(String, ${params.map((p) => p.type).join(', ')})`, bytes); const result = {}; for (const [i, param] of params.entries()) { result[param.name] = payload[i + 1].toJSON(); } return result; }, fromCode: (code, ...args) => { if (!this._api) { throw new Error('API is not set. Use .setApi method to set API instance'); } const builder = new transactionBuilder.TransactionBuilder(this._api, this.registry, 'upload_program', undefined, func.name, [...args], `(${params.map((p) => p.type).join(', ')})`, 'String', code); this._programId = builder.programId; return builder; }, fromCodeId: (codeId, ...args) => { if (!this._api) { throw new Error('API is not set. Use .setApi method to set API instance'); } const builder = new transactionBuilder.TransactionBuilder(this._api, this.registry, 'create_program', undefined, func.name, [...args], `(${params.map((p) => p.type).join(', ')})`, 'String', codeId); this._programId = builder.programId; return builder; }, docs: func.docs, }; } return funcs; } /** #### Parsed IDL */ get program() { if (!this._program) { throw new Error('IDL is not parsed'); } return this._program; } /** #### Get type definition by name */ getTypeDef(name) { return this.program.getTypeByName(name).def; } } exports.Sails = Sails;