sememe 0.0.0-stage → 0.4.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +2777 -2
- package/bin/sememe.mjs +556 -0
- package/index.mjs +48 -0
- package/interface/ata-ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL.json +62 -0
- package/interface/native-11111111111111111111111111111111.json +226 -0
- package/interface/native-ComputeBudget111111111111111111111111111111.json +72 -0
- package/interface/raydium-675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8.json +352 -0
- package/interface/token-TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA.json +334 -0
- package/interface/token-TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb.json +535 -0
- package/interface/token-metadata.json +132 -0
- package/package.json +71 -4
- package/src/account-list.mjs +308 -0
- package/src/account-union.mjs +523 -0
- package/src/account.mjs +199 -0
- package/src/acquire.mjs +145 -0
- package/src/adopt.mjs +206 -0
- package/src/ata-interface.mjs +276 -0
- package/src/ata.mjs +223 -0
- package/src/base58.mjs +96 -0
- package/src/canonical.mjs +24 -0
- package/src/cli-surface.mjs +300 -0
- package/src/codec.mjs +1118 -0
- package/src/conformance.mjs +431 -0
- package/src/corpus/harvest-compute-budget-2026-10-31.json +19122 -0
- package/src/corpus/harvest-system-2026-10-31.json +26090 -0
- package/src/decode.mjs +286 -0
- package/src/dialect.mjs +180 -0
- package/src/discriminators.mjs +192 -0
- package/src/drift.mjs +172 -0
- package/src/gap.mjs +205 -0
- package/src/lighthouse.mjs +168 -0
- package/src/lock.mjs +440 -0
- package/src/native-audit.mjs +56 -0
- package/src/native-corpus.mjs +57 -0
- package/src/native-interface.mjs +252 -0
- package/src/native.mjs +324 -0
- package/src/peer.mjs +95 -0
- package/src/program-id.mjs +74 -0
- package/src/pull.mjs +117 -0
- package/src/rawle.mjs +454 -0
- package/src/raydium-interface.mjs +747 -0
- package/src/raydium.mjs +391 -0
- package/src/receipt-digest.mjs +84 -0
- package/src/receipt-name.mjs +128 -0
- package/src/receipt-order.mjs +56 -0
- package/src/shank.mjs +73 -0
- package/src/short-forms.mjs +209 -0
- package/src/store-read.mjs +253 -0
- package/src/store-write.mjs +46 -0
- package/src/token-interface.mjs +394 -0
- package/src/token-metadata-interface.mjs +371 -0
- package/src/token-metadata.mjs +317 -0
- package/src/token.mjs +443 -0
- package/src/union.mjs +428 -0
- package/vendor/anza-solana-sdk/8db406e2edfafa25939ec782b77b5b6f30de164d/LICENSE.txt +201 -0
- package/vendor/anza-solana-sdk/8db406e2edfafa25939ec782b77b5b6f30de164d/compute-budget-interface/src/lib.rs +93 -0
- package/vendor/anza-solana-sdk/8db406e2edfafa25939ec782b77b5b6f30de164d/system-interface/src/instruction.rs +1899 -0
- package/vendor/lighthouse/4c579479c98635e419b1b167f08be02a71604a71/LICENSE +21 -0
- package/vendor/lighthouse/4c579479c98635e419b1b167f08be02a71604a71/lighthouse.json +2769 -0
- package/vendor/raydium-amm/91739d8d9f5cf347e0ce14ebe4d2bb7069fb31b6/LICENSE +201 -0
- package/vendor/raydium-amm/91739d8d9f5cf347e0ce14ebe4d2bb7069fb31b6/program/src/instruction.rs +1189 -0
- package/vendor/raydium-amm/91739d8d9f5cf347e0ce14ebe4d2bb7069fb31b6/program/src/state.rs +1348 -0
- package/vendor/spl-associated-token-account/fbf5379e7398ac8b231c73ee8a9ff677b43dd0ec/LICENSE +201 -0
- package/vendor/spl-associated-token-account/fbf5379e7398ac8b231c73ee8a9ff677b43dd0ec/interface/src/instruction.rs +169 -0
- package/vendor/spl-associated-token-account/fbf5379e7398ac8b231c73ee8a9ff677b43dd0ec/interface/src/lib.rs +12 -0
- package/vendor/spl-associated-token-account/fbf5379e7398ac8b231c73ee8a9ff677b43dd0ec/program/src/processor.rs +308 -0
- package/vendor/spl-token/737788ff5bcc3f1f44cfb222f25483187650be76/LICENSE.txt +176 -0
- package/vendor/spl-token/737788ff5bcc3f1f44cfb222f25483187650be76/interface/src/instruction.rs +1963 -0
- package/vendor/spl-token-2022/5251dcc07ecbcb721d6c4f0231090defadf00311/LICENSE.txt +176 -0
- package/vendor/spl-token-2022/5251dcc07ecbcb721d6c4f0231090defadf00311/interface/src/instruction.rs +2868 -0
- package/vendor/spl-token-metadata-interface/7038969ec74a89f1afdb9bc97c112d4d26fead9a/LICENSE +201 -0
- package/vendor/spl-token-metadata-interface/7038969ec74a89f1afdb9bc97c112d4d26fead9a/interface/src/error.rs +58 -0
- package/vendor/spl-token-metadata-interface/7038969ec74a89f1afdb9bc97c112d4d26fead9a/interface/src/instruction.rs +596 -0
- package/vendor/spl-token-metadata-interface/7038969ec74a89f1afdb9bc97c112d4d26fead9a/interface/src/lib.rs +21 -0
- package/vendor/spl-token-metadata-interface/7038969ec74a89f1afdb9bc97c112d4d26fead9a/interface/src/state.rs +229 -0
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
//! Instruction types
|
|
2
|
+
|
|
3
|
+
use {
|
|
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")]
|
|
15
|
+
use {
|
|
16
|
+
serde::{Deserialize, Serialize},
|
|
17
|
+
serde_with::DisplayFromStr,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/// Initialization instruction data
|
|
21
|
+
#[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")]
|
|
25
|
+
pub struct Initialize {
|
|
26
|
+
/// Longer name of the token
|
|
27
|
+
pub name: String,
|
|
28
|
+
/// Shortened symbol of the token
|
|
29
|
+
pub symbol: String,
|
|
30
|
+
/// URI pointing to more metadata (image, video, etc.)
|
|
31
|
+
pub uri: String,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// Update field instruction data
|
|
35
|
+
#[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")]
|
|
39
|
+
pub struct UpdateField {
|
|
40
|
+
/// Field to update in the metadata
|
|
41
|
+
pub field: Field,
|
|
42
|
+
/// Value to write for the field
|
|
43
|
+
pub value: String,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Remove key instruction data
|
|
47
|
+
#[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")]
|
|
51
|
+
pub struct RemoveKey {
|
|
52
|
+
/// If the idempotent flag is set to true, then the instruction will not
|
|
53
|
+
/// error if the key does not exist
|
|
54
|
+
pub idempotent: bool,
|
|
55
|
+
/// Key to remove in the additional metadata portion
|
|
56
|
+
pub key: String,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// Update authority instruction data
|
|
60
|
+
#[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")]
|
|
64
|
+
pub struct UpdateAuthority {
|
|
65
|
+
/// New authority for the token metadata, or unset if `None`
|
|
66
|
+
#[cfg_attr(
|
|
67
|
+
feature = "serde-traits",
|
|
68
|
+
serde(with = "serde_with::As::<Option<DisplayFromStr>>")
|
|
69
|
+
)]
|
|
70
|
+
pub new_authority: MaybeNull<Address>,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// Instruction data for Emit
|
|
74
|
+
#[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")]
|
|
78
|
+
pub struct Emit {
|
|
79
|
+
/// Start of range of data to emit
|
|
80
|
+
pub start: Option<u64>,
|
|
81
|
+
/// End of range of data to emit
|
|
82
|
+
pub end: Option<u64>,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/// All instructions that must be implemented in the token-metadata interface
|
|
86
|
+
#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))]
|
|
87
|
+
#[cfg_attr(feature = "serde-traits", serde(rename_all = "camelCase"))]
|
|
88
|
+
#[derive(Clone, Debug, PartialEq)]
|
|
89
|
+
pub enum TokenMetadataInstruction {
|
|
90
|
+
/// Initializes a TLV entry with the basic token-metadata fields.
|
|
91
|
+
///
|
|
92
|
+
/// Assumes that the provided mint is an SPL token mint, that the metadata
|
|
93
|
+
/// account is allocated and assigned to the program, and that the metadata
|
|
94
|
+
/// account has enough lamports to cover the rent-exempt reserve.
|
|
95
|
+
///
|
|
96
|
+
/// Accounts expected by this instruction:
|
|
97
|
+
///
|
|
98
|
+
/// 0. `[w]` Metadata
|
|
99
|
+
/// 1. `[]` Update authority
|
|
100
|
+
/// 2. `[]` Mint
|
|
101
|
+
/// 3. `[s]` Mint authority
|
|
102
|
+
///
|
|
103
|
+
/// Data: `Initialize` data, name / symbol / uri strings
|
|
104
|
+
Initialize(Initialize),
|
|
105
|
+
|
|
106
|
+
/// Updates a field in a token-metadata account.
|
|
107
|
+
///
|
|
108
|
+
/// The field can be one of the required fields (name, symbol, URI), or a
|
|
109
|
+
/// totally new field denoted by a "key" string.
|
|
110
|
+
///
|
|
111
|
+
/// By the end of the instruction, the metadata account must be properly
|
|
112
|
+
/// re-sized based on the new size of the TLV entry.
|
|
113
|
+
/// * If the new size is larger, the program must first reallocate to
|
|
114
|
+
/// avoid overwriting other TLV entries.
|
|
115
|
+
/// * If the new size is smaller, the program must reallocate at the end
|
|
116
|
+
/// so that it's possible to iterate over TLV entries
|
|
117
|
+
///
|
|
118
|
+
/// Accounts expected by this instruction:
|
|
119
|
+
///
|
|
120
|
+
/// 0. `[w]` Metadata account
|
|
121
|
+
/// 1. `[s]` Update authority
|
|
122
|
+
///
|
|
123
|
+
/// Data: `UpdateField` data, specifying the new field and value. If the
|
|
124
|
+
/// field does not exist on the account, it will be created. If the
|
|
125
|
+
/// field does exist, it will be overwritten.
|
|
126
|
+
UpdateField(UpdateField),
|
|
127
|
+
|
|
128
|
+
/// Removes a key-value pair in a token-metadata account.
|
|
129
|
+
///
|
|
130
|
+
/// This only applies to additional fields, and not the base name / symbol /
|
|
131
|
+
/// URI fields.
|
|
132
|
+
///
|
|
133
|
+
/// By the end of the instruction, the metadata account must be properly
|
|
134
|
+
/// re-sized at the end based on the new size of the TLV entry.
|
|
135
|
+
///
|
|
136
|
+
/// Accounts expected by this instruction:
|
|
137
|
+
///
|
|
138
|
+
/// 0. `[w]` Metadata account
|
|
139
|
+
/// 1. `[s]` Update authority
|
|
140
|
+
///
|
|
141
|
+
/// Data: the string key to remove. If the idempotent flag is set to false,
|
|
142
|
+
/// returns an error if the key is not present
|
|
143
|
+
RemoveKey(RemoveKey),
|
|
144
|
+
|
|
145
|
+
/// Updates the token-metadata authority
|
|
146
|
+
///
|
|
147
|
+
/// Accounts expected by this instruction:
|
|
148
|
+
///
|
|
149
|
+
/// 0. `[w]` Metadata account
|
|
150
|
+
/// 1. `[s]` Current update authority
|
|
151
|
+
///
|
|
152
|
+
/// Data: the new authority. Can be unset using a `None` value
|
|
153
|
+
UpdateAuthority(UpdateAuthority),
|
|
154
|
+
|
|
155
|
+
/// Emits the token-metadata as return data
|
|
156
|
+
///
|
|
157
|
+
/// The format of the data emitted follows exactly the `TokenMetadata`
|
|
158
|
+
/// struct, but it's possible that the account data is stored in another
|
|
159
|
+
/// format by the program.
|
|
160
|
+
///
|
|
161
|
+
/// With this instruction, a program that implements the token-metadata
|
|
162
|
+
/// interface can return `TokenMetadata` without adhering to the specific
|
|
163
|
+
/// byte layout of the `TokenMetadata` struct in any accounts.
|
|
164
|
+
///
|
|
165
|
+
/// Accounts expected by this instruction:
|
|
166
|
+
///
|
|
167
|
+
/// 0. `[]` Metadata account
|
|
168
|
+
Emit(Emit),
|
|
169
|
+
}
|
|
170
|
+
/// Validates that a Borsh-encoded `String` starting at `*cursor` declares a
|
|
171
|
+
/// length that fits within `rest`, advancing `*cursor` past it on success.
|
|
172
|
+
///
|
|
173
|
+
/// `TokenMetadataInstruction::unpack` calls this before `try_from_slice` so a
|
|
174
|
+
/// forged length prefix can't trigger an oversized allocation that aborts on
|
|
175
|
+
/// the SBF heap. See <https://github.com/solana-program/token-2022/issues/1152>.
|
|
176
|
+
fn check_borsh_string(rest: &[u8], cursor: &mut usize) -> Result<(), ProgramError> {
|
|
177
|
+
let len_end = cursor
|
|
178
|
+
.checked_add(core::mem::size_of::<u32>())
|
|
179
|
+
.ok_or(ProgramError::InvalidInstructionData)?;
|
|
180
|
+
let len_bytes = rest
|
|
181
|
+
.get(*cursor..len_end)
|
|
182
|
+
.ok_or(ProgramError::InvalidInstructionData)?;
|
|
183
|
+
let len = u32::from_le_bytes(len_bytes.try_into().unwrap()) as usize;
|
|
184
|
+
let str_end = len_end
|
|
185
|
+
.checked_add(len)
|
|
186
|
+
.ok_or(ProgramError::InvalidInstructionData)?;
|
|
187
|
+
if str_end > rest.len() {
|
|
188
|
+
return Err(ProgramError::InvalidInstructionData);
|
|
189
|
+
}
|
|
190
|
+
*cursor = str_end;
|
|
191
|
+
Ok(())
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
impl TokenMetadataInstruction {
|
|
195
|
+
/// Unpacks a byte buffer into a
|
|
196
|
+
/// [`TokenMetadataInstruction`](enum.TokenMetadataInstruction.html).
|
|
197
|
+
pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
|
|
198
|
+
if input.len() < ArrayDiscriminator::LENGTH {
|
|
199
|
+
return Err(ProgramError::InvalidInstructionData);
|
|
200
|
+
}
|
|
201
|
+
let (discriminator, rest) = input.split_at(ArrayDiscriminator::LENGTH);
|
|
202
|
+
Ok(match discriminator {
|
|
203
|
+
Initialize::SPL_DISCRIMINATOR_SLICE => {
|
|
204
|
+
let mut cursor = 0usize;
|
|
205
|
+
check_borsh_string(rest, &mut cursor)?; // name
|
|
206
|
+
check_borsh_string(rest, &mut cursor)?; // symbol
|
|
207
|
+
check_borsh_string(rest, &mut cursor)?; // uri
|
|
208
|
+
let data = Initialize::try_from_slice(rest)?;
|
|
209
|
+
Self::Initialize(data)
|
|
210
|
+
}
|
|
211
|
+
UpdateField::SPL_DISCRIMINATOR_SLICE => {
|
|
212
|
+
let mut cursor = 0usize;
|
|
213
|
+
// `Field` is a Borsh enum: 1-byte variant tag, and only the
|
|
214
|
+
// `Key` variant (tag 3) carries a nested string.
|
|
215
|
+
let tag = *rest
|
|
216
|
+
.get(cursor)
|
|
217
|
+
.ok_or(ProgramError::InvalidInstructionData)?;
|
|
218
|
+
cursor = cursor
|
|
219
|
+
.checked_add(1)
|
|
220
|
+
.ok_or(ProgramError::InvalidInstructionData)?;
|
|
221
|
+
if tag == 3 {
|
|
222
|
+
check_borsh_string(rest, &mut cursor)?; // Field::Key(String)
|
|
223
|
+
}
|
|
224
|
+
check_borsh_string(rest, &mut cursor)?; // value
|
|
225
|
+
let data = UpdateField::try_from_slice(rest)?;
|
|
226
|
+
Self::UpdateField(data)
|
|
227
|
+
}
|
|
228
|
+
RemoveKey::SPL_DISCRIMINATOR_SLICE => {
|
|
229
|
+
let mut cursor = 1usize; // skip the 1-byte `idempotent` bool
|
|
230
|
+
check_borsh_string(rest, &mut cursor)?; // key
|
|
231
|
+
let data = RemoveKey::try_from_slice(rest)?;
|
|
232
|
+
Self::RemoveKey(data)
|
|
233
|
+
}
|
|
234
|
+
UpdateAuthority::SPL_DISCRIMINATOR_SLICE => {
|
|
235
|
+
let data = UpdateAuthority::try_from_slice(rest)?;
|
|
236
|
+
Self::UpdateAuthority(data)
|
|
237
|
+
}
|
|
238
|
+
Emit::SPL_DISCRIMINATOR_SLICE => {
|
|
239
|
+
let data = Emit::try_from_slice(rest)?;
|
|
240
|
+
Self::Emit(data)
|
|
241
|
+
}
|
|
242
|
+
_ => return Err(ProgramError::InvalidInstructionData),
|
|
243
|
+
})
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/// Packs a [`TokenMetadataInstruction`](enum.TokenMetadataInstruction.html)
|
|
247
|
+
/// into a byte buffer.
|
|
248
|
+
pub fn pack(&self) -> Vec<u8> {
|
|
249
|
+
let mut buf = vec![];
|
|
250
|
+
match self {
|
|
251
|
+
Self::Initialize(data) => {
|
|
252
|
+
buf.extend_from_slice(Initialize::SPL_DISCRIMINATOR_SLICE);
|
|
253
|
+
buf.append(&mut borsh::to_vec(data).unwrap());
|
|
254
|
+
}
|
|
255
|
+
Self::UpdateField(data) => {
|
|
256
|
+
buf.extend_from_slice(UpdateField::SPL_DISCRIMINATOR_SLICE);
|
|
257
|
+
buf.append(&mut borsh::to_vec(data).unwrap());
|
|
258
|
+
}
|
|
259
|
+
Self::RemoveKey(data) => {
|
|
260
|
+
buf.extend_from_slice(RemoveKey::SPL_DISCRIMINATOR_SLICE);
|
|
261
|
+
buf.append(&mut borsh::to_vec(data).unwrap());
|
|
262
|
+
}
|
|
263
|
+
Self::UpdateAuthority(data) => {
|
|
264
|
+
buf.extend_from_slice(UpdateAuthority::SPL_DISCRIMINATOR_SLICE);
|
|
265
|
+
buf.append(&mut borsh::to_vec(data).unwrap());
|
|
266
|
+
}
|
|
267
|
+
Self::Emit(data) => {
|
|
268
|
+
buf.extend_from_slice(Emit::SPL_DISCRIMINATOR_SLICE);
|
|
269
|
+
buf.append(&mut borsh::to_vec(data).unwrap());
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
buf
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/// Creates an `Initialize` instruction
|
|
277
|
+
#[allow(clippy::too_many_arguments)]
|
|
278
|
+
pub fn initialize(
|
|
279
|
+
program_id: &Address,
|
|
280
|
+
metadata: &Address,
|
|
281
|
+
update_authority: &Address,
|
|
282
|
+
mint: &Address,
|
|
283
|
+
mint_authority: &Address,
|
|
284
|
+
name: String,
|
|
285
|
+
symbol: String,
|
|
286
|
+
uri: String,
|
|
287
|
+
) -> Instruction {
|
|
288
|
+
let data = TokenMetadataInstruction::Initialize(Initialize { name, symbol, uri });
|
|
289
|
+
Instruction {
|
|
290
|
+
program_id: *program_id,
|
|
291
|
+
accounts: vec![
|
|
292
|
+
AccountMeta::new(*metadata, false),
|
|
293
|
+
AccountMeta::new_readonly(*update_authority, false),
|
|
294
|
+
AccountMeta::new_readonly(*mint, false),
|
|
295
|
+
AccountMeta::new_readonly(*mint_authority, true),
|
|
296
|
+
],
|
|
297
|
+
data: data.pack(),
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/// Creates an `UpdateField` instruction
|
|
302
|
+
pub fn update_field(
|
|
303
|
+
program_id: &Address,
|
|
304
|
+
metadata: &Address,
|
|
305
|
+
update_authority: &Address,
|
|
306
|
+
field: Field,
|
|
307
|
+
value: String,
|
|
308
|
+
) -> Instruction {
|
|
309
|
+
let data = TokenMetadataInstruction::UpdateField(UpdateField { field, value });
|
|
310
|
+
Instruction {
|
|
311
|
+
program_id: *program_id,
|
|
312
|
+
accounts: vec![
|
|
313
|
+
AccountMeta::new(*metadata, false),
|
|
314
|
+
AccountMeta::new_readonly(*update_authority, true),
|
|
315
|
+
],
|
|
316
|
+
data: data.pack(),
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/// Creates a `RemoveKey` instruction
|
|
321
|
+
pub fn remove_key(
|
|
322
|
+
program_id: &Address,
|
|
323
|
+
metadata: &Address,
|
|
324
|
+
update_authority: &Address,
|
|
325
|
+
key: String,
|
|
326
|
+
idempotent: bool,
|
|
327
|
+
) -> Instruction {
|
|
328
|
+
let data = TokenMetadataInstruction::RemoveKey(RemoveKey { key, idempotent });
|
|
329
|
+
Instruction {
|
|
330
|
+
program_id: *program_id,
|
|
331
|
+
accounts: vec![
|
|
332
|
+
AccountMeta::new(*metadata, false),
|
|
333
|
+
AccountMeta::new_readonly(*update_authority, true),
|
|
334
|
+
],
|
|
335
|
+
data: data.pack(),
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/// Creates an `UpdateAuthority` instruction
|
|
340
|
+
pub fn update_authority(
|
|
341
|
+
program_id: &Address,
|
|
342
|
+
metadata: &Address,
|
|
343
|
+
current_authority: &Address,
|
|
344
|
+
new_authority: MaybeNull<Address>,
|
|
345
|
+
) -> Instruction {
|
|
346
|
+
let data = TokenMetadataInstruction::UpdateAuthority(UpdateAuthority { new_authority });
|
|
347
|
+
Instruction {
|
|
348
|
+
program_id: *program_id,
|
|
349
|
+
accounts: vec![
|
|
350
|
+
AccountMeta::new(*metadata, false),
|
|
351
|
+
AccountMeta::new_readonly(*current_authority, true),
|
|
352
|
+
],
|
|
353
|
+
data: data.pack(),
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/// Creates an `Emit` instruction
|
|
358
|
+
pub fn emit(
|
|
359
|
+
program_id: &Address,
|
|
360
|
+
metadata: &Address,
|
|
361
|
+
start: Option<u64>,
|
|
362
|
+
end: Option<u64>,
|
|
363
|
+
) -> Instruction {
|
|
364
|
+
let data = TokenMetadataInstruction::Emit(Emit { start, end });
|
|
365
|
+
Instruction {
|
|
366
|
+
program_id: *program_id,
|
|
367
|
+
accounts: vec![AccountMeta::new_readonly(*metadata, false)],
|
|
368
|
+
data: data.pack(),
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
#[cfg(test)]
|
|
373
|
+
mod test {
|
|
374
|
+
#[cfg(feature = "serde-traits")]
|
|
375
|
+
use core::str::FromStr;
|
|
376
|
+
use {
|
|
377
|
+
super::*,
|
|
378
|
+
crate::NAMESPACE,
|
|
379
|
+
alloc::{format, string::ToString, vec},
|
|
380
|
+
solana_sha256_hasher::hashv,
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
fn check_pack_unpack<T: BorshSerialize>(
|
|
384
|
+
instruction: TokenMetadataInstruction,
|
|
385
|
+
discriminator: &[u8],
|
|
386
|
+
data: T,
|
|
387
|
+
) {
|
|
388
|
+
let mut expect = vec![];
|
|
389
|
+
expect.extend_from_slice(discriminator.as_ref());
|
|
390
|
+
expect.append(&mut borsh::to_vec(&data).unwrap());
|
|
391
|
+
let packed = instruction.pack();
|
|
392
|
+
assert_eq!(packed, expect);
|
|
393
|
+
let unpacked = TokenMetadataInstruction::unpack(&expect).unwrap();
|
|
394
|
+
assert_eq!(unpacked, instruction);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
#[test]
|
|
398
|
+
fn initialize_pack() {
|
|
399
|
+
let name = "My test token";
|
|
400
|
+
let symbol = "TEST";
|
|
401
|
+
let uri = "http://test.test";
|
|
402
|
+
let data = Initialize {
|
|
403
|
+
name: name.to_string(),
|
|
404
|
+
symbol: symbol.to_string(),
|
|
405
|
+
uri: uri.to_string(),
|
|
406
|
+
};
|
|
407
|
+
let check = TokenMetadataInstruction::Initialize(data.clone());
|
|
408
|
+
let preimage = hashv(&[format!("{NAMESPACE}:initialize_account").as_bytes()]);
|
|
409
|
+
let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH];
|
|
410
|
+
check_pack_unpack(check, discriminator, data);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
#[test]
|
|
414
|
+
fn update_field_pack() {
|
|
415
|
+
let field = "MyTestField";
|
|
416
|
+
let value = "http://test.uri";
|
|
417
|
+
let data = UpdateField {
|
|
418
|
+
field: Field::Key(field.to_string()),
|
|
419
|
+
value: value.to_string(),
|
|
420
|
+
};
|
|
421
|
+
let check = TokenMetadataInstruction::UpdateField(data.clone());
|
|
422
|
+
let preimage = hashv(&[format!("{NAMESPACE}:updating_field").as_bytes()]);
|
|
423
|
+
let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH];
|
|
424
|
+
check_pack_unpack(check, discriminator, data);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
#[test]
|
|
428
|
+
fn remove_key_pack() {
|
|
429
|
+
let data = RemoveKey {
|
|
430
|
+
key: "MyTestField".to_string(),
|
|
431
|
+
idempotent: true,
|
|
432
|
+
};
|
|
433
|
+
let check = TokenMetadataInstruction::RemoveKey(data.clone());
|
|
434
|
+
let preimage = hashv(&[format!("{NAMESPACE}:remove_key_ix").as_bytes()]);
|
|
435
|
+
let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH];
|
|
436
|
+
check_pack_unpack(check, discriminator, data);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
#[test]
|
|
440
|
+
fn update_authority_pack() {
|
|
441
|
+
let data = UpdateAuthority {
|
|
442
|
+
new_authority: MaybeNull::default(),
|
|
443
|
+
};
|
|
444
|
+
let check = TokenMetadataInstruction::UpdateAuthority(data.clone());
|
|
445
|
+
let preimage = hashv(&[format!("{NAMESPACE}:update_the_authority").as_bytes()]);
|
|
446
|
+
let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH];
|
|
447
|
+
check_pack_unpack(check, discriminator, data);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
#[test]
|
|
451
|
+
fn emit_pack() {
|
|
452
|
+
let data = Emit {
|
|
453
|
+
start: None,
|
|
454
|
+
end: Some(10),
|
|
455
|
+
};
|
|
456
|
+
let check = TokenMetadataInstruction::Emit(data.clone());
|
|
457
|
+
let preimage = hashv(&[format!("{NAMESPACE}:emitter").as_bytes()]);
|
|
458
|
+
let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH];
|
|
459
|
+
check_pack_unpack(check, discriminator, data);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
#[test]
|
|
463
|
+
fn fail_unpack_initialize_with_forged_name_length() {
|
|
464
|
+
let mut input = vec![];
|
|
465
|
+
input.extend_from_slice(Initialize::SPL_DISCRIMINATOR_SLICE);
|
|
466
|
+
input.extend_from_slice(&u32::MAX.to_le_bytes()); // forged name length, no bytes follow
|
|
467
|
+
assert_eq!(
|
|
468
|
+
TokenMetadataInstruction::unpack(&input).unwrap_err(),
|
|
469
|
+
ProgramError::InvalidInstructionData,
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
#[test]
|
|
474
|
+
fn fail_unpack_update_field_with_forged_value_length() {
|
|
475
|
+
let mut input = vec![];
|
|
476
|
+
input.extend_from_slice(UpdateField::SPL_DISCRIMINATOR_SLICE);
|
|
477
|
+
input.push(0u8); // Field::Name (tag 0, no nested string)
|
|
478
|
+
input.extend_from_slice(&u32::MAX.to_le_bytes()); // forged value length
|
|
479
|
+
assert_eq!(
|
|
480
|
+
TokenMetadataInstruction::unpack(&input).unwrap_err(),
|
|
481
|
+
ProgramError::InvalidInstructionData,
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
#[test]
|
|
486
|
+
fn fail_unpack_remove_key_with_forged_key_length() {
|
|
487
|
+
let mut input = vec![];
|
|
488
|
+
input.extend_from_slice(RemoveKey::SPL_DISCRIMINATOR_SLICE);
|
|
489
|
+
input.push(0u8); // idempotent = false
|
|
490
|
+
input.extend_from_slice(&u32::MAX.to_le_bytes()); // forged key length
|
|
491
|
+
assert_eq!(
|
|
492
|
+
TokenMetadataInstruction::unpack(&input).unwrap_err(),
|
|
493
|
+
ProgramError::InvalidInstructionData,
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
#[cfg(feature = "serde-traits")]
|
|
498
|
+
#[test]
|
|
499
|
+
fn initialize_serde() {
|
|
500
|
+
let data = Initialize {
|
|
501
|
+
name: "Token Name".to_string(),
|
|
502
|
+
symbol: "TST".to_string(),
|
|
503
|
+
uri: "uri.test".to_string(),
|
|
504
|
+
};
|
|
505
|
+
let ix = TokenMetadataInstruction::Initialize(data);
|
|
506
|
+
let serialized = serde_json::to_string(&ix).unwrap();
|
|
507
|
+
let serialized_expected =
|
|
508
|
+
"{\"initialize\":{\"name\":\"Token Name\",\"symbol\":\"TST\",\"uri\":\"uri.test\"}}";
|
|
509
|
+
assert_eq!(&serialized, serialized_expected);
|
|
510
|
+
|
|
511
|
+
let deserialized = serde_json::from_str::<TokenMetadataInstruction>(&serialized).unwrap();
|
|
512
|
+
assert_eq!(ix, deserialized);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
#[cfg(feature = "serde-traits")]
|
|
516
|
+
#[test]
|
|
517
|
+
fn update_field_serde() {
|
|
518
|
+
let data = UpdateField {
|
|
519
|
+
field: Field::Key("MyField".to_string()),
|
|
520
|
+
value: "my field value".to_string(),
|
|
521
|
+
};
|
|
522
|
+
let ix = TokenMetadataInstruction::UpdateField(data);
|
|
523
|
+
let serialized = serde_json::to_string(&ix).unwrap();
|
|
524
|
+
let serialized_expected =
|
|
525
|
+
"{\"updateField\":{\"field\":{\"key\":\"MyField\"},\"value\":\"my field value\"}}";
|
|
526
|
+
assert_eq!(&serialized, serialized_expected);
|
|
527
|
+
|
|
528
|
+
let deserialized = serde_json::from_str::<TokenMetadataInstruction>(&serialized).unwrap();
|
|
529
|
+
assert_eq!(ix, deserialized);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
#[cfg(feature = "serde-traits")]
|
|
533
|
+
#[test]
|
|
534
|
+
fn remove_key_serde() {
|
|
535
|
+
let data = RemoveKey {
|
|
536
|
+
key: "MyTestField".to_string(),
|
|
537
|
+
idempotent: true,
|
|
538
|
+
};
|
|
539
|
+
let ix = TokenMetadataInstruction::RemoveKey(data);
|
|
540
|
+
let serialized = serde_json::to_string(&ix).unwrap();
|
|
541
|
+
let serialized_expected = "{\"removeKey\":{\"idempotent\":true,\"key\":\"MyTestField\"}}";
|
|
542
|
+
assert_eq!(&serialized, serialized_expected);
|
|
543
|
+
|
|
544
|
+
let deserialized = serde_json::from_str::<TokenMetadataInstruction>(&serialized).unwrap();
|
|
545
|
+
assert_eq!(ix, deserialized);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
#[cfg(feature = "serde-traits")]
|
|
549
|
+
#[test]
|
|
550
|
+
fn update_authority_serde() {
|
|
551
|
+
let update_authority_option: Option<Address> =
|
|
552
|
+
Some(Address::from_str("4uQeVj5tqViQh7yWWGStvkEG1Zmhx6uasJtWCJziofM").unwrap());
|
|
553
|
+
let update_authority: MaybeNull<Address> = update_authority_option.try_into().unwrap();
|
|
554
|
+
let data = UpdateAuthority {
|
|
555
|
+
new_authority: update_authority,
|
|
556
|
+
};
|
|
557
|
+
let ix = TokenMetadataInstruction::UpdateAuthority(data);
|
|
558
|
+
let serialized = serde_json::to_string(&ix).unwrap();
|
|
559
|
+
let serialized_expected = "{\"updateAuthority\":{\"newAuthority\":\"4uQeVj5tqViQh7yWWGStvkEG1Zmhx6uasJtWCJziofM\"}}";
|
|
560
|
+
assert_eq!(&serialized, serialized_expected);
|
|
561
|
+
|
|
562
|
+
let deserialized = serde_json::from_str::<TokenMetadataInstruction>(&serialized).unwrap();
|
|
563
|
+
assert_eq!(ix, deserialized);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
#[cfg(feature = "serde-traits")]
|
|
567
|
+
#[test]
|
|
568
|
+
fn update_authority_serde_with_none() {
|
|
569
|
+
let data = UpdateAuthority {
|
|
570
|
+
new_authority: MaybeNull::default(),
|
|
571
|
+
};
|
|
572
|
+
let ix = TokenMetadataInstruction::UpdateAuthority(data);
|
|
573
|
+
let serialized = serde_json::to_string(&ix).unwrap();
|
|
574
|
+
let serialized_expected = "{\"updateAuthority\":{\"newAuthority\":null}}";
|
|
575
|
+
assert_eq!(&serialized, serialized_expected);
|
|
576
|
+
|
|
577
|
+
let deserialized = serde_json::from_str::<TokenMetadataInstruction>(&serialized).unwrap();
|
|
578
|
+
assert_eq!(ix, deserialized);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
#[cfg(feature = "serde-traits")]
|
|
582
|
+
#[test]
|
|
583
|
+
fn emit_serde() {
|
|
584
|
+
let data = Emit {
|
|
585
|
+
start: None,
|
|
586
|
+
end: Some(10),
|
|
587
|
+
};
|
|
588
|
+
let ix = TokenMetadataInstruction::Emit(data);
|
|
589
|
+
let serialized = serde_json::to_string(&ix).unwrap();
|
|
590
|
+
let serialized_expected = "{\"emit\":{\"start\":null,\"end\":10}}";
|
|
591
|
+
assert_eq!(&serialized, serialized_expected);
|
|
592
|
+
|
|
593
|
+
let deserialized = serde_json::from_str::<TokenMetadataInstruction>(&serialized).unwrap();
|
|
594
|
+
assert_eq!(ix, deserialized);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//! Crate defining an interface for token-metadata
|
|
2
|
+
|
|
3
|
+
#![allow(clippy::arithmetic_side_effects)]
|
|
4
|
+
#![no_std]
|
|
5
|
+
#![deny(missing_docs)]
|
|
6
|
+
#![cfg_attr(not(test), forbid(unsafe_code))]
|
|
7
|
+
|
|
8
|
+
extern crate alloc;
|
|
9
|
+
|
|
10
|
+
pub mod error;
|
|
11
|
+
pub mod instruction;
|
|
12
|
+
pub mod state;
|
|
13
|
+
|
|
14
|
+
// Export current sdk types for downstream users building with a different sdk
|
|
15
|
+
// version Export borsh for downstream users
|
|
16
|
+
pub use {
|
|
17
|
+
borsh, solana_address, solana_borsh, solana_instruction, solana_nullable, solana_program_error,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/// Namespace for all programs implementing token-metadata
|
|
21
|
+
pub const NAMESPACE: &str = "spl_token_metadata_interface";
|