js-moi-logic
Version:
Module to interact with MOI Logic Objects.
265 lines (229 loc) • 9.96 kB
text/typescript
import { ElementDescriptor, LogicManifest, ManifestCoder } from "js-moi-manifest";
import type { AbstractProvider, LogicActionPayload, LogicDeployPayload, LogicPayload, Sender } from "js-moi-providers";
import { InteractionCallResponse, InteractionObject, InteractionResponse } from "js-moi-providers";
import { Signer } from "js-moi-signer";
import { ErrorCode, ErrorUtils, OpType } from "js-moi-utils";
import { LogicIxArguments, LogicIxObject, LogicIxResponse } from "../types/interaction";
import { LogicIxRequest } from "../types/logic";
import { LogicId } from "./logic-id";
import { RoutineOption } from "./routine-options";
/**
* The default fuel price used for logic interactions.
*/
const DEFAULT_FUEL_PRICE = 1;
/**
* This abstract class extends the ElementDescriptor class and serves as a base
* class for logic-related operations.
* It defines common properties and abstract methods that subclasses should implement.
*/
export abstract class LogicBase extends ElementDescriptor {
protected signer?: Signer;
protected provider: AbstractProvider;
protected manifestCoder: ManifestCoder;
constructor(manifest: LogicManifest.Manifest, signer: Signer) {
super(manifest.elements)
this.manifestCoder = new ManifestCoder(manifest);
this.connect(signer)
}
// abstract methods to be implemented by subclasses
protected abstract createPayload(ixObject: LogicIxObject): LogicDeployPayload | LogicActionPayload;
// TODO: Logic Call Result should be handled seperately
protected abstract processResult(response: LogicIxResponse, timeout?: number): Promise<unknown | null>;
/**
* Returns the logic ID associated with the LogicBase instance.
*
* @returns {string} The logic ID.
*/
protected getLogicId(): LogicId {
return new LogicId("")
}
/**
* Returns the interaction type based on the routine kind.
*
* @returns {OpType} The interaction type.
*/
protected getTxType(kind: string): OpType.LOGIC_DEPLOY | OpType.LOGIC_INVOKE |
OpType.LOGIC_ENLIST {
switch(kind){
case "deploy":
return OpType.LOGIC_DEPLOY;
case "invoke":
return OpType.LOGIC_INVOKE;
case "enlist":
return OpType.LOGIC_ENLIST;
default:
throw new Error("Unsupported routine kind!");
}
}
/**
* Updates the signer and provider instances for the LogicBase instance.
*
* @param {Signer | AbstractProvider} signer - The signer or provider instance.
*/
public connect(signer: Signer): void {
if (signer instanceof Signer) {
this.signer = signer;
this.provider = signer.getProvider();
return;
}
this.provider = signer;
}
/**
* Executes a routine with the given arguments and returns the interaction response.
*
* @param {any} ixObject - The interaction object.
* @param {any[]} args - The arguments for the routine.
* @returns {Promise<InteractionResponse>} A promise that resolves to the
* interaction response.
* @throws {Error} if the provider is not initialized within the signer,
* if the logic id is not defined, if the method type is unsupported,
* or if the sendInteraction operation fails.
*/
protected async executeRoutine(ixObject: LogicIxObject, method: string, option: RoutineOption): Promise<InteractionCallResponse | number | bigint | InteractionResponse> {
if (this.getTxType(ixObject.routine.kind) !== OpType.LOGIC_DEPLOY && !this.getLogicId()) {
ErrorUtils.throwError(
"This logic object doesn't have logic id assigned yet, please assign an logic id.",
ErrorCode.NOT_INITIALIZED
);
}
const { type, params } = await this.processArguments(ixObject, method, option);
switch (type) {
case "call": {
const response = await this.provider.call(params);
return {
...response,
result: this.processResult.bind(this, {
...response,
routine_name: ixObject.routine.name,
}),
};
}
case "estimate": {
if (!this.signer?.isInitialized()) {
ErrorUtils.throwError(
"Mutating routine calls require a signer to be initialized.",
ErrorCode.NOT_INITIALIZED
);
}
return this.provider.estimateFuel(params);
}
case "send": {
if (!this.signer?.isInitialized()) {
ErrorUtils.throwError(
"Mutating routine calls require a signer to be initialized.",
ErrorCode.NOT_INITIALIZED
);
}
const response = await this.signer.sendInteraction(params);
return {
...response,
result: this.processResult.bind(this, {
...response,
routine_name: ixObject.routine.name,
}),
};
}
default:
break;
}
ErrorUtils.throwError('Method "' + type + '" not supported.',ErrorCode.UNSUPPORTED_OPERATION);
}
/**
* Processes the interaction arguments and returns the processed arguments object.
*
* @param {LogicIxObject} ixObject - The interaction object.
* @param {any[]} args - The interaction arguments.
* @returns {any} The processed arguments object.
* @throws {Error} Throws an error if there are missing arguments or missing fuel information.
*/
protected async processArguments(ixObject: LogicIxObject, type: string, option: RoutineOption): Promise<LogicIxArguments> {
const params: InteractionObject = {
sender: option.sender ?? {
id: ((await this.signer?.getIdentifier()).toString()),
key_id: (await this.signer?.getKeyId()),
sequence: option.sequence != null ? option.sequence : (await this.signer?.getNonce()),
} as Sender,
fuel_price: option.fuelPrice,
fuel_limit: option.fuelLimit,
ix_operations: [],
participants: option.participants ?? [],
}
const opType = this.getTxType(ixObject.routine.kind);
const payload = ixObject.createPayload();
switch (opType) {
case OpType.LOGIC_DEPLOY:
params.ix_operations = [
{
type: OpType.LOGIC_DEPLOY,
payload: payload as LogicDeployPayload,
},
];
break;
case OpType.LOGIC_INVOKE:
case OpType.LOGIC_ENLIST:
params.ix_operations = [
{
type: opType,
payload: payload as LogicActionPayload,
},
];
break;
default:
ErrorUtils.throwError(
`Unsupported operation type: ${opType}`,
ErrorCode.UNSUPPORTED_OPERATION
);
}
return { type, params }
}
/**
* Creates a logic interaction request object based on the given interaction object.
*
* @param {LogicIxObject} ixObject - The interaction object.
* @returns {LogicIxRequest} The logic interaction request object.
*/
protected createIxRequest(ixObject: LogicIxObject): LogicIxRequest {
const unwrap = async () => {
const ix = await ixObject.call();
return await ix.result();
}
return {
unwrap,
call: ixObject.call.bind(ixObject),
send: ixObject.send.bind(ixObject),
estimateFuel: ixObject.estimateFuel.bind(ixObject)
}
}
/**
* Creates a logic interaction request object with the specified routine and arguments.
*
* @param {LogicManifest.Routine} routine - The routine for the logic interaction request.
* @param {any[]} args - The arguments for the logic interaction request.
* @returns {LogicIxRequest} The logic interaction request object.
*/
protected createIxObject(routine: LogicManifest.Routine, ...args: any[]): LogicIxRequest {
const option = args.at(-1) && args.at(-1) instanceof RoutineOption ? args.pop() : {};
const ixObject: LogicIxObject = {
routine: routine,
arguments: args
} as LogicIxObject
ixObject.call = async (): Promise<InteractionCallResponse> => {
option.particpants = option.participants ?? [];
return this.executeRoutine(ixObject, "call", option) as Promise<InteractionCallResponse>
}
ixObject.send = async (): Promise<InteractionResponse> => {
option.fuelLimit = option.fuelLimit ?? await ixObject.estimateFuel();
option.fuelPrice = option.fuelPrice ?? DEFAULT_FUEL_PRICE;
option.particpants = option.participants ?? [];
return this.executeRoutine(ixObject, "send", option) as Promise<InteractionResponse>
}
ixObject.estimateFuel = (): Promise<number|bigint> => {
option.particpants = option.participants ?? [];
return this.executeRoutine(ixObject, "estimate", option) as Promise<number | bigint>
}
ixObject.createPayload = (): LogicPayload => {
return this.createPayload(ixObject) as LogicPayload
}
return this.createIxRequest(ixObject);
}
}