@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.
- package/README.md +9 -9
- package/js/approve/live-check.mjs +106 -1
- package/js/approve/page.html +1 -1
- package/js/approve/page.js +19 -5
- package/js/approve/server.mjs +192 -33
- package/js/chain/metadata.js +92 -0
- package/js/client/compressed.js +12 -5
- package/js/client/idl/loot.js +1 -1
- package/js/client/idl/loot.json +53 -7
- package/js/client/ix.js +44 -9
- package/js/economy/check.mjs +12 -4
- package/js/economy/cost.mjs +6 -5
- package/js/economy/explain.mjs +3 -2
- package/js/economy/guide.mjs +2 -2
- package/js/economy/live.mjs +27 -7
- package/js/economy/model.mjs +13 -2
- package/js/economy/plan.mjs +84 -27
- package/js/economy/schema.json +7 -1
- package/js/mcp/tools.mjs +31 -11
- package/js/play/index.js +519 -193
- package/package.json +1 -1
- package/release.json +5 -5
- package/skills/xsolana-economy/SKILL.md +4 -4
- package/skills/xsolana-economy/references/apps.md +6 -0
- package/skills/xsolana-economy/references/telling-the-person.md +4 -2
- package/skills/xsolana-economy/references/trust-and-cheating.md +4 -3
- package/types/chain/metadata.d.ts +45 -0
- package/types/client/compressed.d.ts +5 -2
- package/types/client/ix.d.ts +38 -7
- package/types/economy/cost.d.mts +11 -3
- package/types/economy/live.d.mts +29 -5
- package/types/economy/model.d.mts +6 -0
- package/types/economy/plan.d.mts +22 -5
- package/types/play/index.d.ts +214 -129
package/js/chain/metadata.js
CHANGED
|
@@ -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);
|
package/js/client/compressed.js
CHANGED
|
@@ -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
|
|
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
|
|
38
|
-
delegate: asset.ownership
|
|
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
|
}
|