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