import type { Beet } from '@convergence-rfq/beet'; import * as beet from '@convergence-rfq/beet'; import { FailedToDeserializeDataError, FailedToSerializeDataError, UnexpectedAccountError, } from '../errors'; import { Account, MaybeAccount, UnparsedAccount, UnparsedMaybeAccount, } from './Account'; export type Serializer = { description: string; serialize: (value: T) => Buffer; deserialize: (buffer: Buffer, offset?: number) => [T, number]; }; export const mapSerializer = ( serializer: Serializer, map: (value: T) => U, unmap: (value: U) => T ): Serializer => ({ description: serializer.description, serialize: (value) => serializer.serialize(unmap(value)), deserialize: (buffer, offset) => { const [value, newOffset] = serializer.deserialize(buffer, offset); return [map(value), newOffset]; }, }); export const createSerializerFromBeet = ( beetArg: Beet ): Serializer => ({ description: beetArg.description, serialize: (value: T) => { const fixedBeet = beet.isFixableBeet(beetArg) ? beetArg.toFixedFromValue(value) : beetArg; const writer = new beet.BeetWriter(fixedBeet.byteSize); writer.write(fixedBeet, value); return writer.buffer; }, deserialize: (buffer: Buffer, offset?: number) => { const fixedBeet = beet.isFixableBeet(beetArg) ? beetArg.toFixedFromData(buffer, offset ?? 0) : beetArg; const reader = new beet.BeetReader(buffer, offset ?? 0); const value = reader.read(fixedBeet); return [value, reader.offset]; }, }); export const createSerializerFromFixedSizeBeet = ( beetArg: beet.FixedSizeBeet ): Serializer => ({ description: beetArg.description, serialize: (value: T) => { const fixedBeet = beet.isFixableBeet(beetArg) ? beetArg.toFixedFromValue(value) : beetArg; const writer = new beet.BeetWriter(fixedBeet.byteSize); writer.write(fixedBeet, value); return writer.buffer; }, deserialize: (buffer: Buffer, offset?: number) => { const fixedBeet = beet.isFixableBeet(beetArg) ? beetArg.toFixedFromData(buffer, offset ?? 0) : beetArg; const reader = new beet.BeetReader(buffer, offset ?? 0); const value = reader.read(fixedBeet); return [value, reader.offset]; }, }); export const createSerializerFromFixableBeet = ( beetArg: beet.FixableBeet> ): Serializer => ({ description: beetArg.description, serialize: (value: T) => { const fixedBeet = beet.isFixableBeet(beetArg) ? beetArg.toFixedFromValue(value) : beetArg; const writer = new beet.BeetWriter(fixedBeet.byteSize); writer.write(fixedBeet, value); return writer.buffer; }, deserialize: (buffer: Buffer, offset?: number) => { const fixedBeet = beet.isFixableBeet(beetArg) ? beetArg.toFixedFromData(buffer, offset ?? 0) : beetArg; const reader = new beet.BeetReader(buffer, offset ?? 0); const value = reader.read(fixedBeet); return [value, reader.offset]; }, }); export const createSerializerFromFixableBeetArgsStruct = ( beetArg: beet.FixableBeetArgsStruct ): Serializer => ({ description: beetArg.description, serialize: (value: T) => { const fixedBeet = beet.isFixableBeet(beetArg) ? beetArg.toFixedFromValue(value) : beetArg; const writer = new beet.BeetWriter(fixedBeet.byteSize); writer.write(fixedBeet, value); return writer.buffer; }, deserialize: (buffer: Buffer, offset?: number) => { const fixedBeet = beet.isFixableBeet(beetArg) ? beetArg.toFixedFromData(buffer, offset ?? 0) : beetArg; const reader = new beet.BeetReader(buffer, offset ?? 0); const value = reader.read(fixedBeet); return [value, reader.offset]; }, }); export type SolitaType = { name: string; deserialize: (data: Buffer, offset?: number) => [T, number]; fromArgs: (args: T) => { serialize: () => [Buffer, number]; }; }; export const createSerializerFromSolitaType = ( solitaType: SolitaType, description?: string ): Serializer => ({ description: description ?? solitaType.name, serialize: (value: T) => { return solitaType.fromArgs(value).serialize()[0]; }, deserialize: (buffer: Buffer, offset?: number) => { return solitaType.deserialize(buffer, offset); }, }); export const serialize = ( value: T, serializer: Pick, 'description' | 'serialize'> ): Buffer => { try { return serializer.serialize(value); } catch (error) { throw new FailedToSerializeDataError(serializer.description, { cause: error as Error, }); } }; export const deserialize = ( value: Buffer, serializer: Pick, 'description' | 'deserialize'> ): [T, number] => { try { return serializer.deserialize(value); } catch (error) { throw new FailedToDeserializeDataError(serializer.description, { cause: error as Error, }); } }; export function deserializeAccount( account: UnparsedMaybeAccount, serializer: Pick, 'description' | 'deserialize'> ): MaybeAccount; export function deserializeAccount( account: UnparsedAccount, serializer: Pick, 'description' | 'deserialize'> ): Account; export function deserializeAccount( account: UnparsedAccount | UnparsedMaybeAccount, serializer: Pick, 'description' | 'deserialize'> ): Account | MaybeAccount { if ('exists' in account && !account.exists) { return account; } try { const data: T = serializer.deserialize(account.data)[0]; return { ...account, data }; } catch (error) { throw new UnexpectedAccountError( account.publicKey, serializer.description, { cause: error as Error } ); } } export const serializeDiscriminator = (discriminator: number[]): Buffer => { const serializer = createSerializerFromBeet( beet.uniformFixedSizeArray(beet.u8, 8) ); return serialize(discriminator, serializer); };