solana-framework
Version:
solana-framework is solana uni-tools for typescript
138 lines (117 loc) • 4.39 kB
text/typescript
import * as beet from '@metaplex-foundation/beet';
import * as beetSolana from '@metaplex-foundation/beet-solana';
import {
collectionBeet,
collectionDetailsBeet,
programmableConfigBeet,
dataBeet,
keyBeet,
tokenStandardBeet,
usesBeet,
Metadata
} from '../generated';
const NONE_BYTE_SIZE = beet.coptionNone('').byteSize;
/**
* This is a custom deserializer for TokenMetadata in order to mitigate acounts with corrupted
* data on chain.
*
* Instead of failing the deserialization for the section that is possibly corrupt it just returns
* `null` for the fields that would normally be stored in that section.
*
* This deserializer matches the [fix implemented in the Rust program](https://github.com/metaplex-foundation/metaplex-program-library/blob/df36da5a78fb17e1690247b8041b761d27c83b1b/token-metadata/program/src/deser.rs#L6).
* Also @see ../../../program/src/deser.rs
*/
export function deserialize(buf: Buffer, offset = 0): [Metadata, number] {
let cursor = offset;
// key
const key = keyBeet.read(buf, cursor);
cursor += keyBeet.byteSize;
// updateAuthority
const updateAuthority = beetSolana.publicKey.read(buf, cursor);
cursor += beetSolana.publicKey.byteSize;
// mint
const mint = beetSolana.publicKey.read(buf, cursor);
cursor += beetSolana.publicKey.byteSize;
// data
const [data, dataDelta] = dataBeet.deserialize(buf, cursor);
cursor = dataDelta;
// primarySaleHappened
const primarySaleHappened = beet.bool.read(buf, cursor);
cursor += beet.bool.byteSize;
// isMutable
const isMutable = beet.bool.read(buf, cursor);
cursor += beet.bool.byteSize;
// editionNonce
const editionNonceBeet = beet.coption(beet.u8).toFixedFromData(buf, cursor);
const editionNonce = editionNonceBeet.read(buf, cursor);
cursor += editionNonceBeet.byteSize;
// -----------------
// Possibly corrupted section
// -----------------
// NOTE: that we avoid trying to deserialize any subsequent fields if a
// previous one was found to be corrupted just to save work
// tokenStandard
const [tokenStandard, tokenDelta, tokenCorrupted] = tryReadOption(
beet.coption(tokenStandardBeet),
buf,
cursor,
);
cursor += tokenDelta;
// collection
const [collection, collectionDelta, collectionCorrupted] = tokenCorrupted
? [null, NONE_BYTE_SIZE, true]
: tryReadOption(beet.coption(collectionBeet), buf, cursor);
cursor += collectionDelta;
// uses
const [uses, usesDelta, usesCorrupted] =
tokenCorrupted || collectionCorrupted
? [null, NONE_BYTE_SIZE, true]
: tryReadOption(beet.coption(usesBeet), buf, cursor);
cursor += usesDelta;
// collection_details
const [collectionDetails, collectionDetailsDelta, collectionDetailsCorrupted] =
tokenCorrupted || collectionCorrupted || usesCorrupted
? [null, NONE_BYTE_SIZE, true]
: tryReadOption(beet.coption(collectionDetailsBeet), buf, cursor);
cursor += collectionDetailsDelta;
// programmable_config
const [programmableConfig, programmableConfigDelta, programmableConfigCorrupted] =
tokenCorrupted || collectionCorrupted || usesCorrupted
? [null, NONE_BYTE_SIZE, true]
: tryReadOption(beet.coption(programmableConfigBeet), buf, cursor);
cursor += programmableConfigDelta;
const anyCorrupted =
tokenCorrupted ||
collectionCorrupted ||
usesCorrupted ||
collectionDetailsCorrupted ||
programmableConfigCorrupted;
const args = {
key,
updateAuthority,
mint,
data,
primarySaleHappened,
isMutable,
editionNonce,
tokenStandard: anyCorrupted ? null : tokenStandard,
collection: anyCorrupted ? null : collection,
uses: anyCorrupted ? null : uses,
collectionDetails: anyCorrupted ? null : collectionDetails,
programmableConfig: anyCorrupted ? null : programmableConfig,
};
return [Metadata.fromArgs(args), cursor];
}
function tryReadOption<T>(
optionBeet: beet.FixableBeet<T, Partial<T>>,
buf: Buffer,
offset: number,
): [T | null, number, boolean] {
try {
const fixed = optionBeet.toFixedFromData(buf, offset);
const value = fixed.read(buf, offset);
return [value, fixed.byteSize, false];
} catch (e) {
return [null, NONE_BYTE_SIZE, true];
}
}