wowok_agent
Version:
Agent for WoWok: Unlock Co-Creation, Lighting Transaction, Empower Potential.
272 lines (236 loc) • 11.7 kB
text/typescript
import { Entity, Resource, TxbAddress, array_unique, TagName, ResourceObject, PassportObject, Errors, ERROR, Permission,
PermissionIndexType, GuardParser, Passport, WitnessFill, CallResponse, TransactionBlock, WithdrawFee, TreasuryObject
} from 'wowok';
import { query_permission } from '../query/permission.js';
import { Account } from '../local/account.js';
import { ObjectBase, ObjectBaseType, query_objects, query_personal, raw2type} from '../query/objects.js';
import { LocalMark } from '../local/local.js';
export interface Namedbject {
name?: string; // name of the object, if not defined, the object will be created without name
tags?: string[]; // tags of the object, if not defined, the object will be created without tags
// true: use address as the name if the name exist; otherwise, use this name and change the original name to its address
useAddressIfNameExist?: boolean;
onChain?: boolean; // true: onchain mark, false: local mark(default)
}
export interface NamedObjectWithDescription extends Namedbject {
description?:string; // description of the new object, if not defined, the object will be created with no description.
}
export interface NamedObjectWithPermission extends Namedbject {
// permission object, undefined or {named_new...} for creating a new object; if defined, the permission object must exist.
permission?: ObjectParam;
}
export interface TypeNamedObjectWithPermission extends NamedObjectWithPermission {
// type of the object, e.g. '0x2::coin::Coin<0x2::sui::SUI>'
type_parameter: string;
};
/// object address or namedNew for creating a new object
export type ObjectTypedMain = string | TypeNamedObjectWithPermission ;
export type ObjectMain = string | NamedObjectWithPermission ;
export type ObjectPermissionMain = string | Namedbject;
export type ObjectParam = string | NamedObjectWithDescription;
export const GetObjectExisted = (object: ObjectMain | ObjectTypedMain | ObjectParam | ObjectPermissionMain | undefined) : string | undefined => {
return (typeof object === 'string' ) ? object : undefined;
}
export const GetObjectMain = (object: ObjectMain | ObjectTypedMain | ObjectPermissionMain | undefined) : NamedObjectWithPermission | TypeNamedObjectWithPermission | Namedbject | undefined => {
if (typeof object === 'object' && object !== null && 'type_parameter' in object) {
return (object as TypeNamedObjectWithPermission);
} else if (typeof object === 'object' && object !== null && 'permission' in object) {
return (object as NamedObjectWithPermission);
} else if (typeof object === 'object') {
return (object as Namedbject);
}
}
export const GetObjectParam = (object: ObjectParam | undefined) : NamedObjectWithDescription | undefined => {
return (typeof object === 'object' && object!== null && 'description' in object)? (object as NamedObjectWithDescription) : undefined;
}
export type ObjectsOp = {op:'set' | 'add' | 'remove' ; objects:string[]} | {op:'removeall'};
// address from local Account or local Mark.
export type AccountOrMark_Address = {name_or_address?: string, local_mark_first?: boolean} ;
export const GetAccountOrMark_Address = async (entity?: AccountOrMark_Address) : Promise<string | undefined> => {
if (!entity || !entity.name_or_address) {
return (await Account.Instance().get())?.address
}
if (entity?.local_mark_first) {
const r = await LocalMark.Instance().get_address(entity.name_or_address);
if (!r) {
return (await Account.Instance().get(entity.name_or_address))?.address;
}
return r
} else {
const r = (await Account.Instance().get(entity.name_or_address))?.address;
if (!r) {
return await LocalMark.Instance().get_address(entity.name_or_address);
}
return r
}
}
export const GetManyAccountOrMark_Address = async (entities: AccountOrMark_Address[]) : Promise<string[]> => {
const res = [];
for (let i = 0; i < entities.length; ++i) {
const addr = await GetAccountOrMark_Address(entities[i]);
if (addr) res.push(addr);
}
return res;
}
export interface PayParam {
index: bigint | string | number,
remark: string,
for_object?: string,
for_guard?: string,
}
export const SetWithdrawFee = async (param: PayParam, treasury?:TreasuryObject) : Promise<WithdrawFee> => {
if (!treasury) {
ERROR(Errors.InvalidParam, 'WithdrawFee: treasury_address invalid');
}
const [object, guard] = await LocalMark.Instance().get_many_address([param.for_object, param.for_guard]);
return {index:BigInt(param.index), remark:param.remark, for_object:object, for_guard:guard, treasury:treasury};
}
export interface AddressMark {
address: TxbAddress;
name?: string;
tags: string[];
}
export interface ResponseData extends ObjectBase {
change:'created' | 'mutated' | string;
}
export interface GuardInfo_forCall {
guards: string[];
witness: WitnessFill[];
}
export type CallResult = GuardInfo_forCall | CallResponse | undefined;
export function ResponseData(response: CallResponse | undefined) : ResponseData[] {
const res : ResponseData[] = [];
response?.objectChanges?.forEach(v => {
const type_raw: string | undefined = (v as any)?.objectType;
const type = raw2type(type_raw);
if (type) {
res.push({type:type, type_raw:type_raw, object:(v as any)?.objectId, version:(v as any)?.version,
owner:(v as any)?.owner, change:v.type
})
}
})
return res;
}
export class CallBase {
// operation implementation for a call
private resouceObject:ResourceObject | undefined;
private traceMarkNew = new Map<ObjectBaseType, Namedbject>();
content: ObjectBase | undefined = undefined;
protected async operate(txb:TransactionBlock, passport?:PassportObject, account?:string) {};
protected async prepare() {};
constructor () {}
// return WitnessFill to resolve filling witness, and than 'call_with_witness' to complete the call;
// return ResponseData when the call has completed;
// throw an exception when errors.
async call(account?:string) : Promise<CallResult> { return undefined };
async call_with_witness (info: GuardInfo_forCall, account?:string) : Promise<CallResponse | undefined> {
if (info.guards.length > 0) { // prepare passport
const p: GuardParser | undefined = await GuardParser.Create([...info.guards]);
if (p) {
const query = await p.done(info.witness);
if (query) {
const txb = new TransactionBlock();
const passport = new Passport(txb, query!);
await this.prepare();
await this.operate(txb, passport?.get_object(), account)
passport.destroy();
return await this.sign_and_commit(txb, account);
}
} else {
ERROR(Errors.Fail, 'guard verify')
}
}
}
protected async check_permission_and_call (permission:string, permIndex: PermissionIndexType[], guards_needed: string[],
checkOwner?:boolean, checkAdmin?:boolean, account?:string) : Promise<CallResult> {
var guards : string[] = [];
if (permIndex.length > 0 || checkOwner) {
const p = await query_permission({permission_object:permission, address:{name_or_address:account}});
if (checkOwner && !p.owner) ERROR(Errors.noPermission, 'owner');
if (checkAdmin && !p.admin) ERROR(Errors.noPermission, 'admin');
permIndex.forEach(v => {
const r = Permission.HasPermission(p, v);
if (!r?.has) ERROR(Errors.noPermission, v);
if (r?.guard) guards.push(r.guard)
})
}
if (guards_needed.length > 0) {
guards = guards.concat(guards_needed);
}
if (guards.length > 0) { // prepare passport
const p: GuardParser | undefined = await GuardParser.Create([...guards]);
const futures = p ? p.future_fills() : [];
if (!p) ERROR(Errors.Fail, 'guard parse')
if (p && futures.length === 0) {
const query = await p.done();
if (query) {
const txb = new TransactionBlock();
const passport = new Passport(txb, query!);
await this.operate(txb, passport?.get_object(), account)
passport.destroy();
return await this.sign_and_commit(txb, account);
}
}
return {guards:[...guards], witness:p!.future_fills()};
} else { // no passport needed
return await this.exec(account)
}
}
protected async exec (account?:string) : Promise<CallResponse> {
const txb = new TransactionBlock();
await this.operate(txb, undefined, account);
return await this.sign_and_commit(txb, account);
}
protected async new_with_mark(type:ObjectBaseType, txb:TransactionBlock, object:TxbAddress, named_new?:Namedbject, account?:string, innerTags:string[]=[TagName.Launch]) {
const tags = named_new?.tags ? array_unique([...named_new.tags, ...innerTags]) : array_unique([...innerTags]);
if (named_new) {
named_new.tags = tags;
this.traceMarkNew.set(type, named_new)
}
if (!named_new?.onChain) return ;
// onchain mark
if (!this.resouceObject) {
const r = await query_personal({address:{name_or_address:account}});
if (!r?.mark_object) {
this.resouceObject = Entity.From(txb).create_resource2(); // new
Resource.From(txb, this.resouceObject).add(object, tags, named_new?.name);
} else {
Resource.From(txb, r.mark_object).add(object, tags, named_new?.name);
}
} else {
Resource.From(txb, this.resouceObject).add(object, tags, named_new?.name);
}
}
protected async update_content(type:ObjectBaseType, object?:string) {
if (this.content || !object) return ;
const r = await query_objects({objects:[object], no_cache:true});
if (r?.objects?.length !== 1 || r?.objects[0]?.type !== type) {
ERROR(Errors.Fail, `Fetch ${type} object ${object} failed`)
}
this.content = r?.objects[0];
}
protected async sign_and_commit(txb: TransactionBlock, account?: string) : Promise<CallResponse> {
if (this.resouceObject) {
Resource.From(txb, this.resouceObject).launch(); //@ resource launch, if created.
this.resouceObject = undefined;
}
const r = await Account.Instance().sign_and_commit(txb, account);
if (!r) {
ERROR(Errors.Fail, 'sign and commit failed');
}
// save the mark locally, anyway
const res = ResponseData(r);
for (let i = 0; i < res.length; ++i) {
const v = res[i];
if (v.type && v.change === 'created') {
const namedNew = this.traceMarkNew.get(v.type);
if (namedNew) {
await LocalMark.Instance().put(namedNew.name,
{address:v.object, tags:namedNew?.tags ? [...namedNew?.tags, v.type] : [v.type]},
namedNew?.useAddressIfNameExist);
}
}
}
return r
}
}