@xgamesfun/solana 0.26.3 → 0.27.0

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.
@@ -72,5 +72,97 @@ export function createFungibleMetadataIx({ mint, authority, payer = authority, n
72
72
  };
73
73
  }
74
74
 
75
+ const UPDATE_V2 = 15;
76
+ /**
77
+ * Token Metadata's UpdateMetadataAccountV2 for a fungible token's record: a new name, ticker and link ('' for none),
78
+ * all three written as given. `authority` is the record's update authority (for a token a setup made: the project's
79
+ * owner) and signs; it stays the update authority, and the record stays changeable. The record's words are written
80
+ * whole, with no royalty, creators, collection or uses: as a record a setup made has them. One that was given any of
81
+ * those outside these tools would lose them (or refuse the write), so the approval sends this only to a record that
82
+ * has none (`parseRecord`'s `plain`; approve/live-check.mjs readRenames). The mint is untouched, and it costs only the
83
+ * transaction: the record keeps its size.
84
+ */
85
+ export function updateFungibleMetadataIx({ mint, authority, name, symbol, uri = '' }) {
86
+ for (const [field, value] of Object.entries({ name, symbol, uri })) {
87
+ if (typeof value !== 'string') throw new TypeError(`A token's ${field} is text`);
88
+ if (utf8(value).length > METADATA_LIMITS[field])
89
+ throw new RangeError(`A token's ${field} is at most ${METADATA_LIMITS[field]} bytes`);
90
+ }
91
+ return {
92
+ programId: METAPLEX_METADATA_PROGRAM,
93
+ keys: [meta(metadataAddress(mint), false, true), meta(authority, true, false)],
94
+ data: concat(
95
+ Uint8Array.of(UPDATE_V2),
96
+ Uint8Array.of(1), // new data
97
+ text(name),
98
+ text(symbol),
99
+ text(uri),
100
+ uintLE(0, 2), // no resale royalty
101
+ NONE, // no creators
102
+ NONE, // no collection
103
+ NONE, // no uses
104
+ NONE, // the update authority: as it is
105
+ NONE, // primary sale: as it is
106
+ NONE, // changeable: as it is
107
+ ),
108
+ };
109
+ }
110
+
111
+ /**
112
+ * A Token Metadata record as the chain holds it, or null when the data is not one (its first byte says a metadata
113
+ * record; the strings are padded with NULs to their full length):
114
+ * { updateAuthority, mint, name, symbol, uri, royalty, creators, mutable, collection, uses, plain }.
115
+ * `royalty`: its resale royalty, in hundredths of a percent. `creators`: how many it lists. `mutable`: its update
116
+ * authority can still change it. `collection`, `uses`: whether it names one. `plain`: none of royalty, creators,
117
+ * collection or uses, as a setup's own record is: writing its words again changes nothing else of it.
118
+ */
119
+ export function parseRecord(data) {
120
+ try {
121
+ if (!data || data.length < 1 + 32 + 32 + 12 || data[0] !== 4) return null;
122
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
123
+ let at = 65;
124
+ const words = [];
125
+ for (let i = 0; i < 3; i++) {
126
+ const length = view.getUint32(at, true);
127
+ if (at + 4 + length > data.length) return null;
128
+ words.push(new TextDecoder().decode(data.subarray(at + 4, at + 4 + length)).replaceAll('\0', ''));
129
+ at += 4 + length;
130
+ }
131
+ if (at + 3 > data.length) return null;
132
+ const royalty = view.getUint16(at, true);
133
+ at += 2;
134
+ let creators = 0;
135
+ if (data[at++] === 1) {
136
+ creators = view.getUint32(at, true);
137
+ at += 4 + 34 * creators;
138
+ }
139
+ if (at + 2 > data.length) return null;
140
+ const mutable = data[at + 1] === 1;
141
+ at += 2; // sold once, and whether it can be changed
142
+ // What follows is each there or not (a byte), with its value when it is: the edition's nonce, the token's standard,
143
+ // then the collection (verified, and its address) and the uses (a method and two counts). A record cut short
144
+ // before them names neither.
145
+ for (let i = 0; i < 2 && at < data.length; i++) at += data[at] === 1 ? 2 : 1;
146
+ const collection = at < data.length && data[at] === 1;
147
+ at += collection ? 34 : 1;
148
+ const uses = at < data.length && data[at] === 1;
149
+ return {
150
+ updateAuthority: encodeBase58(data.subarray(1, 33)),
151
+ mint: encodeBase58(data.subarray(33, 65)),
152
+ name: words[0],
153
+ symbol: words[1],
154
+ uri: words[2],
155
+ royalty,
156
+ creators,
157
+ mutable,
158
+ collection,
159
+ uses,
160
+ plain: royalty === 0 && creators === 0 && !collection && !uses,
161
+ };
162
+ } catch {
163
+ return null;
164
+ }
165
+ }
166
+
75
167
  /** The update authority a Token Metadata record names (who can change its name, ticker and link). */
76
168
  export const metadataUpdateAuthority = data => (data?.length >= 33 ? encodeBase58(data.subarray(1, 33)) : null);
@@ -3,6 +3,7 @@
3
3
  // indexer (DAS `getAsset` and `getAssetProof`). Browser-safe.
4
4
  import { decodeBase58 } from '../chain/base58.js';
5
5
 
6
+ const NOBODY = '11111111111111111111111111111111';
6
7
  /** mpl-account-compression's tree header: type, version, max buffer size (u32), max depth (u32), authority, slot, padding. */
7
8
  const HEADER = 56;
8
9
 
@@ -25,23 +26,29 @@ const bytes = value =>
25
26
  /**
26
27
  * A compressed NFT from an indexer's answers: `asset` (DAS `getAsset`), `proof` (DAS `getAssetProof`) and the tree
27
28
  * account's bytes (`treeData`, for how much of the proof to pass). Returns { id, tree, nonce, owner, delegate,
28
- * collection (or null), dataHash, creatorHash, assetDataHash (or null), flags, root, proof: [node, …] }. The proof
29
- * must be against the tree's current root when it lands: fetch it just before sending.
29
+ * collection (or null), dataHash, creatorHash, assetDataHash (or null), flags, root, proof: [node, …], burnt }. The
30
+ * proof must be against the tree's current root when it lands: fetch it just before sending. A burned NFT (DAS says
31
+ * `burnt`) is described as the program takes one: a data hash of all zeros, which `proveLeafIx` then proves against
32
+ * the leaf's empty slot, so its record names no owner (a record that holds it lets go of it on that proof).
30
33
  */
31
34
  export function compressedFromDas({ asset, proof, treeData }) {
32
35
  const c = asset.compression;
36
+ const burnt = asset.burnt === true || c.burnt === true;
37
+ // A burned leaf names nobody: any address stands in for an owner an indexer no longer gives.
38
+ const owner = asset.ownership?.owner || (burnt ? NOBODY : asset.ownership.owner);
33
39
  return {
34
40
  id: asset.id,
35
41
  tree: c.tree,
36
42
  nonce: BigInt(c.leaf_id),
37
- owner: asset.ownership.owner,
38
- delegate: asset.ownership.delegate ?? asset.ownership.owner,
43
+ owner,
44
+ delegate: asset.ownership?.delegate ?? owner,
39
45
  collection: asset.grouping?.find(g => g.group_key === 'collection')?.group_value ?? null,
40
- dataHash: bytes(c.data_hash),
46
+ dataHash: burnt ? new Uint8Array(32) : bytes(c.data_hash),
41
47
  creatorHash: bytes(c.creator_hash),
42
48
  assetDataHash: bytes(c.asset_data_hash ?? null),
43
49
  flags: Number(c.flags ?? 0),
44
50
  root: bytes(proof.root),
45
51
  proof: proof.proof.slice(0, proofLength(treeData)),
52
+ burnt,
46
53
  };
47
54
  }