1#[cfg(feature = "serde-traits")]
4use serde::{Deserialize, Serialize};
5use {
6 crate::state::Field,
7 borsh::{BorshDeserialize, BorshSerialize},
8 solana_instruction::{AccountMeta, Instruction},
9 solana_program_error::ProgramError,
10 solana_pubkey::Pubkey,
11 spl_discriminator::{discriminator::ArrayDiscriminator, SplDiscriminate},
12 spl_pod::optional_keys::OptionalNonZeroPubkey,
13};
14
15#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))]
17#[cfg_attr(feature = "serde-traits", serde(rename_all = "camelCase"))]
18#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, SplDiscriminate)]
19#[discriminator_hash_input("spl_token_metadata_interface:initialize_account")]
20pub struct Initialize {
21 pub name: String,
23 pub symbol: String,
25 pub uri: String,
27}
28
29#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))]
31#[cfg_attr(feature = "serde-traits", serde(rename_all = "camelCase"))]
32#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, SplDiscriminate)]
33#[discriminator_hash_input("spl_token_metadata_interface:updating_field")]
34pub struct UpdateField {
35 pub field: Field,
37 pub value: String,
39}
40
41#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))]
43#[cfg_attr(feature = "serde-traits", serde(rename_all = "camelCase"))]
44#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, SplDiscriminate)]
45#[discriminator_hash_input("spl_token_metadata_interface:remove_key_ix")]
46pub struct RemoveKey {
47 pub idempotent: bool,
50 pub key: String,
52}
53
54#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, SplDiscriminate)]
56#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))]
57#[cfg_attr(feature = "serde-traits", serde(rename_all = "camelCase"))]
58#[discriminator_hash_input("spl_token_metadata_interface:update_the_authority")]
59pub struct UpdateAuthority {
60 pub new_authority: OptionalNonZeroPubkey,
62}
63
64#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))]
66#[cfg_attr(feature = "serde-traits", serde(rename_all = "camelCase"))]
67#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, SplDiscriminate)]
68#[discriminator_hash_input("spl_token_metadata_interface:emitter")]
69pub struct Emit {
70 pub start: Option<u64>,
72 pub end: Option<u64>,
74}
75
76#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))]
78#[cfg_attr(feature = "serde-traits", serde(rename_all = "camelCase"))]
79#[derive(Clone, Debug, PartialEq)]
80pub enum TokenMetadataInstruction {
81 Initialize(Initialize),
96
97 UpdateField(UpdateField),
118
119 RemoveKey(RemoveKey),
135
136 UpdateAuthority(UpdateAuthority),
145
146 Emit(Emit),
160}
161impl TokenMetadataInstruction {
162 pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
165 if input.len() < ArrayDiscriminator::LENGTH {
166 return Err(ProgramError::InvalidInstructionData);
167 }
168 let (discriminator, rest) = input.split_at(ArrayDiscriminator::LENGTH);
169 Ok(match discriminator {
170 Initialize::SPL_DISCRIMINATOR_SLICE => {
171 let data = Initialize::try_from_slice(rest)?;
172 Self::Initialize(data)
173 }
174 UpdateField::SPL_DISCRIMINATOR_SLICE => {
175 let data = UpdateField::try_from_slice(rest)?;
176 Self::UpdateField(data)
177 }
178 RemoveKey::SPL_DISCRIMINATOR_SLICE => {
179 let data = RemoveKey::try_from_slice(rest)?;
180 Self::RemoveKey(data)
181 }
182 UpdateAuthority::SPL_DISCRIMINATOR_SLICE => {
183 let data = UpdateAuthority::try_from_slice(rest)?;
184 Self::UpdateAuthority(data)
185 }
186 Emit::SPL_DISCRIMINATOR_SLICE => {
187 let data = Emit::try_from_slice(rest)?;
188 Self::Emit(data)
189 }
190 _ => return Err(ProgramError::InvalidInstructionData),
191 })
192 }
193
194 pub fn pack(&self) -> Vec<u8> {
197 let mut buf = vec![];
198 match self {
199 Self::Initialize(data) => {
200 buf.extend_from_slice(Initialize::SPL_DISCRIMINATOR_SLICE);
201 buf.append(&mut borsh::to_vec(data).unwrap());
202 }
203 Self::UpdateField(data) => {
204 buf.extend_from_slice(UpdateField::SPL_DISCRIMINATOR_SLICE);
205 buf.append(&mut borsh::to_vec(data).unwrap());
206 }
207 Self::RemoveKey(data) => {
208 buf.extend_from_slice(RemoveKey::SPL_DISCRIMINATOR_SLICE);
209 buf.append(&mut borsh::to_vec(data).unwrap());
210 }
211 Self::UpdateAuthority(data) => {
212 buf.extend_from_slice(UpdateAuthority::SPL_DISCRIMINATOR_SLICE);
213 buf.append(&mut borsh::to_vec(data).unwrap());
214 }
215 Self::Emit(data) => {
216 buf.extend_from_slice(Emit::SPL_DISCRIMINATOR_SLICE);
217 buf.append(&mut borsh::to_vec(data).unwrap());
218 }
219 };
220 buf
221 }
222}
223
224#[allow(clippy::too_many_arguments)]
226pub fn initialize(
227 program_id: &Pubkey,
228 metadata: &Pubkey,
229 update_authority: &Pubkey,
230 mint: &Pubkey,
231 mint_authority: &Pubkey,
232 name: String,
233 symbol: String,
234 uri: String,
235) -> Instruction {
236 let data = TokenMetadataInstruction::Initialize(Initialize { name, symbol, uri });
237 Instruction {
238 program_id: *program_id,
239 accounts: vec![
240 AccountMeta::new(*metadata, false),
241 AccountMeta::new_readonly(*update_authority, false),
242 AccountMeta::new_readonly(*mint, false),
243 AccountMeta::new_readonly(*mint_authority, true),
244 ],
245 data: data.pack(),
246 }
247}
248
249pub fn update_field(
251 program_id: &Pubkey,
252 metadata: &Pubkey,
253 update_authority: &Pubkey,
254 field: Field,
255 value: String,
256) -> Instruction {
257 let data = TokenMetadataInstruction::UpdateField(UpdateField { field, value });
258 Instruction {
259 program_id: *program_id,
260 accounts: vec![
261 AccountMeta::new(*metadata, false),
262 AccountMeta::new_readonly(*update_authority, true),
263 ],
264 data: data.pack(),
265 }
266}
267
268pub fn remove_key(
270 program_id: &Pubkey,
271 metadata: &Pubkey,
272 update_authority: &Pubkey,
273 key: String,
274 idempotent: bool,
275) -> Instruction {
276 let data = TokenMetadataInstruction::RemoveKey(RemoveKey { key, idempotent });
277 Instruction {
278 program_id: *program_id,
279 accounts: vec![
280 AccountMeta::new(*metadata, false),
281 AccountMeta::new_readonly(*update_authority, true),
282 ],
283 data: data.pack(),
284 }
285}
286
287pub fn update_authority(
289 program_id: &Pubkey,
290 metadata: &Pubkey,
291 current_authority: &Pubkey,
292 new_authority: OptionalNonZeroPubkey,
293) -> Instruction {
294 let data = TokenMetadataInstruction::UpdateAuthority(UpdateAuthority { new_authority });
295 Instruction {
296 program_id: *program_id,
297 accounts: vec![
298 AccountMeta::new(*metadata, false),
299 AccountMeta::new_readonly(*current_authority, true),
300 ],
301 data: data.pack(),
302 }
303}
304
305pub fn emit(
307 program_id: &Pubkey,
308 metadata: &Pubkey,
309 start: Option<u64>,
310 end: Option<u64>,
311) -> Instruction {
312 let data = TokenMetadataInstruction::Emit(Emit { start, end });
313 Instruction {
314 program_id: *program_id,
315 accounts: vec![AccountMeta::new_readonly(*metadata, false)],
316 data: data.pack(),
317 }
318}
319
320#[cfg(test)]
321mod test {
322 #[cfg(feature = "serde-traits")]
323 use std::str::FromStr;
324 use {super::*, crate::NAMESPACE, solana_sha256_hasher::hashv};
325
326 fn check_pack_unpack<T: BorshSerialize>(
327 instruction: TokenMetadataInstruction,
328 discriminator: &[u8],
329 data: T,
330 ) {
331 let mut expect = vec![];
332 expect.extend_from_slice(discriminator.as_ref());
333 expect.append(&mut borsh::to_vec(&data).unwrap());
334 let packed = instruction.pack();
335 assert_eq!(packed, expect);
336 let unpacked = TokenMetadataInstruction::unpack(&expect).unwrap();
337 assert_eq!(unpacked, instruction);
338 }
339
340 #[test]
341 fn initialize_pack() {
342 let name = "My test token";
343 let symbol = "TEST";
344 let uri = "http://test.test";
345 let data = Initialize {
346 name: name.to_string(),
347 symbol: symbol.to_string(),
348 uri: uri.to_string(),
349 };
350 let check = TokenMetadataInstruction::Initialize(data.clone());
351 let preimage = hashv(&[format!("{NAMESPACE}:initialize_account").as_bytes()]);
352 let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH];
353 check_pack_unpack(check, discriminator, data);
354 }
355
356 #[test]
357 fn update_field_pack() {
358 let field = "MyTestField";
359 let value = "http://test.uri";
360 let data = UpdateField {
361 field: Field::Key(field.to_string()),
362 value: value.to_string(),
363 };
364 let check = TokenMetadataInstruction::UpdateField(data.clone());
365 let preimage = hashv(&[format!("{NAMESPACE}:updating_field").as_bytes()]);
366 let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH];
367 check_pack_unpack(check, discriminator, data);
368 }
369
370 #[test]
371 fn remove_key_pack() {
372 let data = RemoveKey {
373 key: "MyTestField".to_string(),
374 idempotent: true,
375 };
376 let check = TokenMetadataInstruction::RemoveKey(data.clone());
377 let preimage = hashv(&[format!("{NAMESPACE}:remove_key_ix").as_bytes()]);
378 let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH];
379 check_pack_unpack(check, discriminator, data);
380 }
381
382 #[test]
383 fn update_authority_pack() {
384 let data = UpdateAuthority {
385 new_authority: OptionalNonZeroPubkey::default(),
386 };
387 let check = TokenMetadataInstruction::UpdateAuthority(data.clone());
388 let preimage = hashv(&[format!("{NAMESPACE}:update_the_authority").as_bytes()]);
389 let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH];
390 check_pack_unpack(check, discriminator, data);
391 }
392
393 #[test]
394 fn emit_pack() {
395 let data = Emit {
396 start: None,
397 end: Some(10),
398 };
399 let check = TokenMetadataInstruction::Emit(data.clone());
400 let preimage = hashv(&[format!("{NAMESPACE}:emitter").as_bytes()]);
401 let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH];
402 check_pack_unpack(check, discriminator, data);
403 }
404
405 #[cfg(feature = "serde-traits")]
406 #[test]
407 fn initialize_serde() {
408 let data = Initialize {
409 name: "Token Name".to_string(),
410 symbol: "TST".to_string(),
411 uri: "uri.test".to_string(),
412 };
413 let ix = TokenMetadataInstruction::Initialize(data);
414 let serialized = serde_json::to_string(&ix).unwrap();
415 let serialized_expected =
416 "{\"initialize\":{\"name\":\"Token Name\",\"symbol\":\"TST\",\"uri\":\"uri.test\"}}";
417 assert_eq!(&serialized, serialized_expected);
418
419 let deserialized = serde_json::from_str::<TokenMetadataInstruction>(&serialized).unwrap();
420 assert_eq!(ix, deserialized);
421 }
422
423 #[cfg(feature = "serde-traits")]
424 #[test]
425 fn update_field_serde() {
426 let data = UpdateField {
427 field: Field::Key("MyField".to_string()),
428 value: "my field value".to_string(),
429 };
430 let ix = TokenMetadataInstruction::UpdateField(data);
431 let serialized = serde_json::to_string(&ix).unwrap();
432 let serialized_expected =
433 "{\"updateField\":{\"field\":{\"key\":\"MyField\"},\"value\":\"my field value\"}}";
434 assert_eq!(&serialized, serialized_expected);
435
436 let deserialized = serde_json::from_str::<TokenMetadataInstruction>(&serialized).unwrap();
437 assert_eq!(ix, deserialized);
438 }
439
440 #[cfg(feature = "serde-traits")]
441 #[test]
442 fn remove_key_serde() {
443 let data = RemoveKey {
444 key: "MyTestField".to_string(),
445 idempotent: true,
446 };
447 let ix = TokenMetadataInstruction::RemoveKey(data);
448 let serialized = serde_json::to_string(&ix).unwrap();
449 let serialized_expected = "{\"removeKey\":{\"idempotent\":true,\"key\":\"MyTestField\"}}";
450 assert_eq!(&serialized, serialized_expected);
451
452 let deserialized = serde_json::from_str::<TokenMetadataInstruction>(&serialized).unwrap();
453 assert_eq!(ix, deserialized);
454 }
455
456 #[cfg(feature = "serde-traits")]
457 #[test]
458 fn update_authority_serde() {
459 let update_authority_option: Option<Pubkey> =
460 Some(Pubkey::from_str("4uQeVj5tqViQh7yWWGStvkEG1Zmhx6uasJtWCJziofM").unwrap());
461 let update_authority: OptionalNonZeroPubkey = update_authority_option.try_into().unwrap();
462 let data = UpdateAuthority {
463 new_authority: update_authority,
464 };
465 let ix = TokenMetadataInstruction::UpdateAuthority(data);
466 let serialized = serde_json::to_string(&ix).unwrap();
467 let serialized_expected = "{\"updateAuthority\":{\"newAuthority\":\"4uQeVj5tqViQh7yWWGStvkEG1Zmhx6uasJtWCJziofM\"}}";
468 assert_eq!(&serialized, serialized_expected);
469
470 let deserialized = serde_json::from_str::<TokenMetadataInstruction>(&serialized).unwrap();
471 assert_eq!(ix, deserialized);
472 }
473
474 #[cfg(feature = "serde-traits")]
475 #[test]
476 fn update_authority_serde_with_none() {
477 let data = UpdateAuthority {
478 new_authority: OptionalNonZeroPubkey::default(),
479 };
480 let ix = TokenMetadataInstruction::UpdateAuthority(data);
481 let serialized = serde_json::to_string(&ix).unwrap();
482 let serialized_expected = "{\"updateAuthority\":{\"newAuthority\":null}}";
483 assert_eq!(&serialized, serialized_expected);
484
485 let deserialized = serde_json::from_str::<TokenMetadataInstruction>(&serialized).unwrap();
486 assert_eq!(ix, deserialized);
487 }
488
489 #[cfg(feature = "serde-traits")]
490 #[test]
491 fn emit_serde() {
492 let data = Emit {
493 start: None,
494 end: Some(10),
495 };
496 let ix = TokenMetadataInstruction::Emit(data);
497 let serialized = serde_json::to_string(&ix).unwrap();
498 let serialized_expected = "{\"emit\":{\"start\":null,\"end\":10}}";
499 assert_eq!(&serialized, serialized_expected);
500
501 let deserialized = serde_json::from_str::<TokenMetadataInstruction>(&serialized).unwrap();
502 assert_eq!(ix, deserialized);
503 }
504}