@mysten/suins
Version:
235 lines (201 loc) • 5.96 kB
text/typescript
import {
bcs,
BcsType,
TypeTag,
TypeTagSerializer,
BcsStruct,
BcsEnum,
BcsTuple,
} from '@mysten/sui/bcs';
import { normalizeSuiAddress } from '@mysten/sui/utils';
import { TransactionArgument, isArgument } from '@mysten/sui/transactions';
import { ClientWithCoreApi, SuiClientTypes } from '@mysten/sui/client';
const MOVE_STDLIB_ADDRESS = normalizeSuiAddress('0x1');
const SUI_FRAMEWORK_ADDRESS = normalizeSuiAddress('0x2');
export type RawTransactionArgument<T> = T | TransactionArgument;
export interface GetOptions<
Include extends Omit<SuiClientTypes.ObjectInclude, 'content'> = {},
> extends SuiClientTypes.GetObjectOptions<Include> {
client: ClientWithCoreApi;
}
export interface GetManyOptions<
Include extends Omit<SuiClientTypes.ObjectInclude, 'content'> = {},
> extends SuiClientTypes.GetObjectsOptions<Include> {
client: ClientWithCoreApi;
}
export function getPureBcsSchema(typeTag: string | TypeTag): BcsType<any> | null {
const parsedTag = typeof typeTag === 'string' ? TypeTagSerializer.parseFromStr(typeTag) : typeTag;
if ('u8' in parsedTag) {
return bcs.U8;
} else if ('u16' in parsedTag) {
return bcs.U16;
} else if ('u32' in parsedTag) {
return bcs.U32;
} else if ('u64' in parsedTag) {
return bcs.U64;
} else if ('u128' in parsedTag) {
return bcs.U128;
} else if ('u256' in parsedTag) {
return bcs.U256;
} else if ('address' in parsedTag) {
return bcs.Address;
} else if ('bool' in parsedTag) {
return bcs.Bool;
} else if ('vector' in parsedTag) {
const type = getPureBcsSchema(parsedTag.vector);
return type ? bcs.vector(type) : null;
} else if ('struct' in parsedTag) {
const structTag = parsedTag.struct;
const pkg = normalizeSuiAddress(structTag.address);
if (pkg === MOVE_STDLIB_ADDRESS) {
if (
(structTag.module === 'ascii' || structTag.module === 'string') &&
structTag.name === 'String'
) {
return bcs.String;
}
if (structTag.module === 'option' && structTag.name === 'Option') {
const type = getPureBcsSchema(structTag.typeParams[0]);
return type ? bcs.option(type) : null;
}
}
if (
pkg === SUI_FRAMEWORK_ADDRESS &&
structTag.module === 'object' &&
(structTag.name === 'ID' || structTag.name === 'UID')
) {
return bcs.Address;
}
}
return null;
}
export function normalizeMoveArguments(
args: unknown[] | object,
argTypes: readonly (string | null)[],
parameterNames?: string[],
) {
const argLen = Array.isArray(args) ? args.length : Object.keys(args).length;
if (parameterNames && argLen !== parameterNames.length) {
throw new Error(
`Invalid number of arguments, expected ${parameterNames.length}, got ${argLen}`,
);
}
const normalizedArgs: TransactionArgument[] = [];
let index = 0;
for (const [i, argType] of argTypes.entries()) {
if (argType === '0x2::clock::Clock') {
normalizedArgs.push((tx) => tx.object.clock());
continue;
}
if (argType === '0x2::random::Random') {
normalizedArgs.push((tx) => tx.object.random());
continue;
}
if (argType === '0x2::deny_list::DenyList') {
normalizedArgs.push((tx) => tx.object.denyList());
continue;
}
if (argType === '0x3::sui_system::SuiSystemState') {
normalizedArgs.push((tx) => tx.object.system());
continue;
}
let arg;
if (Array.isArray(args)) {
if (index >= args.length) {
throw new Error(
`Invalid number of arguments, expected at least ${index + 1}, got ${args.length}`,
);
}
arg = args[index];
} else {
if (!parameterNames) {
throw new Error(`Expected arguments to be passed as an array`);
}
const name = parameterNames[index];
arg = args[name as keyof typeof args];
if (arg === undefined) {
throw new Error(`Parameter ${name} is required`);
}
}
index += 1;
if (typeof arg === 'function' || isArgument(arg)) {
normalizedArgs.push(arg as TransactionArgument);
continue;
}
const type = argTypes[i];
const bcsType = type === null ? null : getPureBcsSchema(type);
if (bcsType) {
const bytes = bcsType.serialize(arg as never);
normalizedArgs.push((tx) => tx.pure(bytes));
continue;
} else if (typeof arg === 'string') {
normalizedArgs.push((tx) => tx.object(arg));
continue;
}
throw new Error(`Invalid argument ${stringify(arg)} for type ${type}`);
}
return normalizedArgs;
}
export class MoveStruct<
T extends Record<string, BcsType<any>>,
const Name extends string = string,
> extends BcsStruct<T, Name> {
async get<Include extends Omit<SuiClientTypes.ObjectInclude, 'content' | 'json'> = {}>({
objectId,
...options
}: GetOptions<Include>): Promise<
SuiClientTypes.Object<Include & { content: true; json: true }> & {
json: BcsStruct<T>['$inferType'];
}
> {
const [res] = await this.getMany<Include>({
...options,
objectIds: [objectId],
});
return res;
}
async getMany<Include extends Omit<SuiClientTypes.ObjectInclude, 'content' | 'json'> = {}>({
client,
...options
}: GetManyOptions<Include>): Promise<
Array<
SuiClientTypes.Object<Include & { content: true; json: true }> & {
json: BcsStruct<T>['$inferType'];
}
>
> {
const response = (await client.core.getObjects({
...options,
include: {
...options.include,
content: true,
},
})) as SuiClientTypes.GetObjectsResponse<Include & { content: true }>;
return response.objects.map((obj) => {
if (obj instanceof Error) {
throw obj;
}
return {
...obj,
json: this.parse(obj.content),
};
});
}
}
export class MoveEnum<
T extends Record<string, BcsType<any> | null>,
const Name extends string,
> extends BcsEnum<T, Name> {}
export class MoveTuple<
const T extends readonly BcsType<any>[],
const Name extends string,
> extends BcsTuple<T, Name> {}
function stringify(val: unknown) {
if (typeof val === 'object') {
return JSON.stringify(val, (val: unknown) => val);
}
if (typeof val === 'bigint') {
return val.toString();
}
return val;
}