Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit b5e20aa

Browse files
authored
token 2022: Expose token-metadata state decoder in @solana/token-metata (#5806)
* only handle deserialization of state, no discriminator or length * fix test case name * addressed pr comments * don't export codec * Added test case with additional metadata * remove comment
1 parent 8c8523d commit b5e20aa

File tree

2 files changed

+82
-78
lines changed

2 files changed

+82
-78
lines changed

token-metadata/js/src/state.ts

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
1-
import { getArrayDecoder, getBytesDecoder, getStructDecoder, getTupleDecoder } from '@solana/codecs-data-structures';
2-
import { getStringDecoder } from '@solana/codecs-strings';
3-
import { TlvState } from '@solana/spl-type-length-value';
41
import { PublicKey } from '@solana/web3.js';
5-
6-
import { TokenMetadataError } from './errors.js';
2+
import { getArrayCodec, getBytesCodec, getStructCodec, getTupleCodec } from '@solana/codecs-data-structures';
3+
import { getStringCodec } from '@solana/codecs-strings';
74

85
export const TOKEN_METADATA_DISCRIMINATOR = Buffer.from([112, 132, 90, 90, 11, 88, 157, 87]);
96

7+
const tokenMetadataCodec = getStructCodec([
8+
['updateAuthority', getBytesCodec({ size: 32 })],
9+
['mint', getBytesCodec({ size: 32 })],
10+
['name', getStringCodec()],
11+
['symbol', getStringCodec()],
12+
['uri', getStringCodec()],
13+
['additionalMetadata', getArrayCodec(getTupleCodec([getStringCodec(), getStringCodec()]))],
14+
]);
15+
1016
export interface TokenMetadata {
1117
// The authority that can sign to update the metadata
1218
updateAuthority?: PublicKey;
@@ -32,22 +38,20 @@ function isNonePubkey(buffer: Uint8Array): boolean {
3238
return true;
3339
}
3440

35-
export function unpack(buffer: Buffer): TokenMetadata {
36-
const tlv = new TlvState(buffer, 8, 4);
37-
const bytes = tlv.firstBytes(TOKEN_METADATA_DISCRIMINATOR);
38-
if (bytes === null) {
39-
throw new TokenMetadataError('Invalid Data');
40-
}
41-
const decoder = getStructDecoder([
42-
['updateAuthority', getBytesDecoder({ size: 32 })],
43-
['mint', getBytesDecoder({ size: 32 })],
44-
['name', getStringDecoder()],
45-
['symbol', getStringDecoder()],
46-
['uri', getStringDecoder()],
47-
['additionalMetadata', getArrayDecoder(getTupleDecoder([getStringDecoder(), getStringDecoder()]))],
48-
]);
41+
// Pack TokenMetadata into byte slab
42+
export const pack = (meta: TokenMetadata): Uint8Array => {
43+
// If no updateAuthority given, set it to the None/Zero PublicKey for encoding
44+
const updateAuthority = meta.updateAuthority ?? PublicKey.default;
45+
return tokenMetadataCodec.encode({
46+
...meta,
47+
updateAuthority: updateAuthority.toBuffer(),
48+
mint: meta.mint.toBuffer(),
49+
});
50+
};
4951

50-
const data = decoder.decode(bytes);
52+
// unpack byte slab into TokenMetadata
53+
export function unpack(buffer: Buffer | Uint8Array): TokenMetadata {
54+
const data = tokenMetadataCodec.decode(buffer);
5155

5256
return isNonePubkey(data[0].updateAuthority)
5357
? {

token-metadata/js/test/state.test.ts

Lines changed: 58 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2,68 +2,31 @@ import { PublicKey } from '@solana/web3.js';
22
import { expect } from 'chai';
33

44
import type { TokenMetadata } from '../src/state';
5-
import { TOKEN_METADATA_DISCRIMINATOR, unpack } from '../src/state';
6-
import { getArrayEncoder, getBytesEncoder, getStructEncoder, getTupleEncoder } from '@solana/codecs-data-structures';
7-
import { getStringEncoder } from '@solana/codecs-strings';
5+
import { unpack, pack } from '../src';
86

97
describe('Token Metadata State', () => {
10-
const lengthBuffer = (buffer: Buffer | Uint8Array): Buffer => {
11-
const length = Buffer.alloc(4);
12-
length.writeUIntLE(buffer.length, 0, 4);
13-
return length;
14-
};
15-
16-
// Helper function to pack meta into tlv bytes slab
17-
const pack = (meta: TokenMetadata) => {
18-
const encoder = getStructEncoder([
19-
['updateAuthority', getBytesEncoder({ size: 32 })],
20-
['mint', getBytesEncoder({ size: 32 })],
21-
['name', getStringEncoder()],
22-
['symbol', getStringEncoder()],
23-
['uri', getStringEncoder()],
24-
['additionalMetadata', getArrayEncoder(getTupleEncoder([getStringEncoder(), getStringEncoder()]))],
25-
]);
26-
const data = encoder.encode({
27-
...meta,
28-
updateAuthority: meta.updateAuthority?.toBuffer(),
29-
mint: meta.mint.toBuffer(),
30-
});
31-
return Buffer.concat([TOKEN_METADATA_DISCRIMINATOR, lengthBuffer(data), data]);
32-
};
33-
34-
it('Can unpack', () => {
35-
const data = Buffer.from([
36-
// From rust implementation
37-
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
38-
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 110, 97,
39-
109, 101, 6, 0, 0, 0, 115, 121, 109, 98, 111, 108, 3, 0, 0, 0, 117, 114, 105, 0, 0, 0, 0,
40-
]);
41-
42-
const input = Buffer.concat([TOKEN_METADATA_DISCRIMINATOR, lengthBuffer(data), data]);
43-
44-
const meta = unpack(input);
45-
expect(meta).to.deep.equal({
8+
it('Can pack and unpack as rust implementation', () => {
9+
const meta = {
4610
mint: PublicKey.default,
4711
name: 'name',
4812
symbol: 'symbol',
4913
uri: 'uri',
5014
additionalMetadata: [],
51-
});
52-
});
15+
};
5316

54-
it('Can unpack with additionalMetadata', () => {
55-
const data = Buffer.from([
56-
// From rust implementation
17+
// From rust implementation
18+
const bytes = Buffer.from([
5719
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
58-
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 110, 101,
59-
119, 95, 110, 97, 109, 101, 10, 0, 0, 0, 110, 101, 119, 95, 115, 121, 109, 98, 111, 108, 7, 0, 0, 0, 110,
60-
101, 119, 95, 117, 114, 105, 2, 0, 0, 0, 4, 0, 0, 0, 107, 101, 121, 49, 6, 0, 0, 0, 118, 97, 108, 117, 101,
61-
49, 4, 0, 0, 0, 107, 101, 121, 50, 6, 0, 0, 0, 118, 97, 108, 117, 101, 50,
20+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 110, 97,
21+
109, 101, 6, 0, 0, 0, 115, 121, 109, 98, 111, 108, 3, 0, 0, 0, 117, 114, 105, 0, 0, 0, 0,
6222
]);
6323

64-
const input = Buffer.concat([TOKEN_METADATA_DISCRIMINATOR, lengthBuffer(data), data]);
65-
const meta = unpack(input);
66-
expect(meta).to.deep.equal({
24+
expect(pack(meta)).to.deep.equal(bytes);
25+
expect(unpack(bytes)).to.deep.equal(meta);
26+
});
27+
28+
it('Can pack and unpack as rust implementation with additionalMetadata', () => {
29+
const meta: TokenMetadata = {
6730
mint: PublicKey.default,
6831
name: 'new_name',
6932
symbol: 'new_symbol',
@@ -72,27 +35,64 @@ describe('Token Metadata State', () => {
7235
['key1', 'value1'],
7336
['key2', 'value2'],
7437
],
75-
});
38+
};
39+
// From rust implementation
40+
const bytes = Buffer.from([
41+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
42+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 110, 101,
43+
119, 95, 110, 97, 109, 101, 10, 0, 0, 0, 110, 101, 119, 95, 115, 121, 109, 98, 111, 108, 7, 0, 0, 0, 110,
44+
101, 119, 95, 117, 114, 105, 2, 0, 0, 0, 4, 0, 0, 0, 107, 101, 121, 49, 6, 0, 0, 0, 118, 97, 108, 117, 101,
45+
49, 4, 0, 0, 0, 107, 101, 121, 50, 6, 0, 0, 0, 118, 97, 108, 117, 101, 50,
46+
]);
47+
48+
expect(pack(meta)).to.deep.equal(bytes);
49+
expect(unpack(bytes)).to.deep.equal(meta);
7650
});
7751

7852
it('Can pack and unpack with mint and updateAuthority', () => {
79-
const input = pack({
53+
const meta = {
8054
updateAuthority: new PublicKey('44444444444444444444444444444444444444444444'),
8155
mint: new PublicKey('55555555555555555555555555555555555555555555'),
8256
name: 'name',
8357
symbol: 'symbol',
8458
uri: 'uri',
8559
additionalMetadata: [],
86-
});
60+
};
61+
62+
const bytes = Buffer.from([
63+
45, 91, 65, 60, 101, 64, 222, 21, 12, 147, 115, 20, 77, 81, 51, 202, 76, 184, 48, 186, 15, 117, 103, 22,
64+
172, 234, 14, 80, 215, 148, 53, 229, 60, 121, 172, 80, 135, 1, 40, 28, 16, 196, 153, 112, 103, 22, 239, 184,
65+
102, 74, 235, 162, 191, 71, 52, 30, 59, 226, 189, 193, 31, 112, 71, 220, 4, 0, 0, 0, 110, 97, 109, 101, 6,
66+
0, 0, 0, 115, 121, 109, 98, 111, 108, 3, 0, 0, 0, 117, 114, 105, 0, 0, 0, 0,
67+
]);
68+
69+
expect(pack(meta)).to.deep.equal(bytes);
70+
expect(unpack(bytes)).to.deep.equal(meta);
71+
});
8772

88-
const meta = unpack(input);
89-
expect(meta).to.deep.equal({
73+
it('Can pack and unpack with mint, updateAuthority and additional metadata', () => {
74+
const meta: TokenMetadata = {
9075
updateAuthority: new PublicKey('44444444444444444444444444444444444444444444'),
9176
mint: new PublicKey('55555555555555555555555555555555555555555555'),
9277
name: 'name',
9378
symbol: 'symbol',
9479
uri: 'uri',
95-
additionalMetadata: [],
96-
});
80+
additionalMetadata: [
81+
['key1', 'value1'],
82+
['key2', 'value2'],
83+
],
84+
};
85+
86+
const bytes = Buffer.from([
87+
45, 91, 65, 60, 101, 64, 222, 21, 12, 147, 115, 20, 77, 81, 51, 202, 76, 184, 48, 186, 15, 117, 103, 22,
88+
172, 234, 14, 80, 215, 148, 53, 229, 60, 121, 172, 80, 135, 1, 40, 28, 16, 196, 153, 112, 103, 22, 239, 184,
89+
102, 74, 235, 162, 191, 71, 52, 30, 59, 226, 189, 193, 31, 112, 71, 220, 4, 0, 0, 0, 110, 97, 109, 101, 6,
90+
0, 0, 0, 115, 121, 109, 98, 111, 108, 3, 0, 0, 0, 117, 114, 105, 2, 0, 0, 0, 4, 0, 0, 0, 107, 101, 121, 49,
91+
6, 0, 0, 0, 118, 97, 108, 117, 101, 49, 4, 0, 0, 0, 107, 101, 121, 50, 6, 0, 0, 0, 118, 97, 108, 117, 101,
92+
50,
93+
]);
94+
95+
expect(pack(meta)).to.deep.equal(bytes);
96+
expect(unpack(bytes)).to.deep.equal(meta);
9797
});
9898
});

0 commit comments

Comments
 (0)