From 7a9eda4373006a5c89ccdd8a765432c023954130 Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 22 Jan 2025 18:12:20 +0200 Subject: [PATCH] feat(ts)!: added support for bcs building for arbitrary types (#220) --- src/common/bcs.ts | 74 ++++++++++++++++++++++++++++++++++- src/common/tx-builder-base.ts | 15 +------ src/common/utils.ts | 26 +++++++++++- 3 files changed, 100 insertions(+), 15 deletions(-) diff --git a/src/common/bcs.ts b/src/common/bcs.ts index 08149f5b..daf2a53f 100644 --- a/src/common/bcs.ts +++ b/src/common/bcs.ts @@ -1,5 +1,7 @@ -import { bcs } from '@mysten/sui/bcs'; +import { bcs, BcsType } from '@mysten/sui/bcs'; +import { SuiClient, SuiMoveNormalizedType } from '@mysten/sui/dist/cjs/client'; import { UID } from './types'; +import { isString } from './utils'; function getCommonStructs() { const Bytes32 = bcs.Address; @@ -329,6 +331,76 @@ function getGasServiceStructs() { }; } +export async function getBcsForStruct( + client: SuiClient, + type: SuiMoveNormalizedType, + typeArguments: SuiMoveNormalizedType[] = [], + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): Promise> { + switch (type) { + case 'Address': + return bcs.Address; + case 'Bool': + return bcs.Bool; + case 'U8': + return bcs.U8; + case 'U16': + return bcs.U16; + case 'U32': + return bcs.U32; + case 'U64': + return bcs.U64; + case 'U128': + return bcs.U128; + case 'U256': + return bcs.U256; + + default: { + } + } + + if (isString(type)) { + return bcs.String; + } + + if ('Vector' in (type as object)) { + return bcs.vector(await getBcsForStruct(client, (type as { Vector: SuiMoveNormalizedType }).Vector, typeArguments)); + } + + if ('Struct' in (type as object)) { + const structType = ( + type as { + Struct: { + address: string; + module: string; + name: string; + typeArguments: SuiMoveNormalizedType[]; + }; + } + ).Struct; + const struct = await client.getNormalizedMoveStruct({ + package: structType.address, + module: structType.module, + struct: structType.name, + }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const fields: Record = {}; + + for (const field of struct.fields) { + fields[field.name] = await getBcsForStruct(client, field.type, structType.typeArguments); + } + + return bcs.struct(structType.name, fields); + } + + if ('TypeParameter' in (type as object)) { + const index = (type as { TypeParameter: number }).TypeParameter; + return await getBcsForStruct(client, typeArguments[index], typeArguments); + } + + throw new Error(`Unsupported type ${type}`); +} + export const bcsStructs = { common: getCommonStructs(), gateway: getGatewayStructs(), diff --git a/src/common/tx-builder-base.ts b/src/common/tx-builder-base.ts index 95d6c757..9ec2c284 100644 --- a/src/common/tx-builder-base.ts +++ b/src/common/tx-builder-base.ts @@ -10,7 +10,8 @@ import { import { Keypair } from '@mysten/sui/cryptography'; import { Transaction, TransactionObjectInput, TransactionResult } from '@mysten/sui/transactions'; import { utils as ethersUtils } from 'ethers'; -import { STD_PACKAGE_ID, SUI_PACKAGE_ID } from '../common/types'; +import { SUI_PACKAGE_ID } from '../common/types'; +import { isString } from './utils'; const { arrayify, hexlify } = ethersUtils; @@ -178,18 +179,6 @@ function isTxContext(parameter: SuiMoveNormalizedType): boolean { return inside.address === SUI_PACKAGE_ID && inside.module === 'tx_context' && inside.name === 'TxContext'; } -function isString(parameter: SuiMoveNormalizedType): boolean { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let asAny = parameter as any; - if (asAny.MutableReference) parameter = asAny.MutableReference; - if (asAny.Reference) asAny = asAny.Reference; - asAny = asAny.Struct; - if (!asAny) return false; - const isAsciiString = asAny.address === STD_PACKAGE_ID && asAny.module === 'ascii' && asAny.name === 'String'; - const isStringString = asAny.address === STD_PACKAGE_ID && asAny.module === 'string' && asAny.name === 'String'; - return isAsciiString || isStringString; -} - export class TxBuilderBase { client: SuiClient; tx: Transaction; diff --git a/src/common/utils.ts b/src/common/utils.ts index 25c9dd42..283a4a72 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -1,7 +1,8 @@ -import { getFullnodeUrl } from '@mysten/sui/client'; +import { getFullnodeUrl, SuiMoveNormalizedType } from '@mysten/sui/client'; import { getFaucetHost, requestSuiFromFaucetV0 } from '@mysten/sui/faucet'; import { arrayify, keccak256 } from 'ethers/lib/utils'; import secp256k1 from 'secp256k1'; +import { STD_PACKAGE_ID } from './types'; export const fundAccountsFromFaucet = async (addresses: string[]) => { const promises = addresses.map(async (address) => { @@ -46,3 +47,26 @@ export function signMessage(privKeys: string[], messageToSign: Uint8Array) { return signatures; } + +export function isString(parameter: SuiMoveNormalizedType): boolean { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let asAny = parameter as any; + + if (asAny.MutableReference) { + parameter = asAny.MutableReference; + } + + if (asAny.Reference) { + asAny = asAny.Reference; + } + + asAny = asAny.Struct; + + if (!asAny) { + return false; + } + + const isAsciiString = asAny.address === STD_PACKAGE_ID && asAny.module === 'ascii' && asAny.name === 'String'; + const isStringString = asAny.address === STD_PACKAGE_ID && asAny.module === 'string' && asAny.name === 'String'; + return isAsciiString || isStringString; +}