@xgamesfun/solana 0.28.0 → 0.28.1

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 CHANGED
@@ -76,7 +76,7 @@ codex plugin add xsolana@xgamesfun
76
76
 
77
77
  **claude.ai, ChatGPT or Claude Desktop:** add `https://xgames.fun/mcp` as a custom connector. Nothing to install.
78
78
 
79
- **Cursor, Cline, opencode or any MCP client:** run `npx -y --package=@xgamesfun/solana@0.28.0 xsolana mcp`.
79
+ **Cursor, Cline, opencode or any MCP client:** run `npx -y --package=@xgamesfun/solana@0.28.1 xsolana mcp`.
80
80
 
81
81
  **Any agent that reads skills** (Copilot, Gemini CLI, Windsurf…): `npx skills add XGamesFun/solana-kit`.
82
82
 
@@ -87,26 +87,26 @@ are in [docs/harnesses.md](docs/harnesses.md).
87
87
 
88
88
  ```bash
89
89
  # find a worked example close to your idea
90
- npx -y @xgamesfun/solana@0.28.0 examples shop with loot boxes
90
+ npx -y @xgamesfun/solana@0.28.1 examples shop with loot boxes
91
91
 
92
92
  # using a coin you already launched? read it from the chain into its "tokens" entry
93
- npx -y @xgamesfun/solana@0.28.0 token <its mint address>
93
+ npx -y @xgamesfun/solana@0.28.1 token <its mint address>
94
94
 
95
95
  # does it work? where does the money go? what does it cost?
96
- npx -y @xgamesfun/solana@0.28.0 check xsolana.json
97
- npx -y @xgamesfun/solana@0.28.0 simulate xsolana.json
98
- npx -y @xgamesfun/solana@0.28.0 cost xsolana.json
96
+ npx -y @xgamesfun/solana@0.28.1 check xsolana.json
97
+ npx -y @xgamesfun/solana@0.28.1 simulate xsolana.json
98
+ npx -y @xgamesfun/solana@0.28.1 cost xsolana.json
99
99
 
100
100
  # the whole economy in plain words, and what a cheater could get
101
- npx -y @xgamesfun/solana@0.28.0 explain xsolana.json
102
- npx -y @xgamesfun/solana@0.28.0 review xsolana.json
101
+ npx -y @xgamesfun/solana@0.28.1 explain xsolana.json
102
+ npx -y @xgamesfun/solana@0.28.1 review xsolana.json
103
103
 
104
104
  # go live: a key file for the project (it prints the address to send devnet SOL to, never the key)…
105
- npx -y @xgamesfun/solana@0.28.0 keygen ~/.config/xsolana/my-game.json
105
+ npx -y @xgamesfun/solana@0.28.1 keygen ~/.config/xsolana/my-game.json
106
106
  # …then everything going live would send, and a code; nothing is sent
107
- npx -y @xgamesfun/solana@0.28.0 approve xsolana.json --keypair ~/.config/xsolana/my-game.json
107
+ npx -y @xgamesfun/solana@0.28.1 approve xsolana.json --keypair ~/.config/xsolana/my-game.json
108
108
  # …and with that code, exactly what it listed is sent
109
- npx -y @xgamesfun/solana@0.28.0 approve xsolana.json --keypair ~/.config/xsolana/my-game.json --yes <code>
109
+ npx -y @xgamesfun/solana@0.28.1 approve xsolana.json --keypair ~/.config/xsolana/my-game.json --yes <code>
110
110
  ```
111
111
 
112
112
  Each command names an exact version, so you always run the release you meant to. `xsolana help` lists them all.
@@ -62,8 +62,16 @@ export function approvalPage(token) {
62
62
  let last = null;
63
63
  // The legal lines the box was last shown with.
64
64
  let legalShown = new Set();
65
+ // How many of the lines about what the wallet added have been said on this page.
66
+ let addedSaid = 0;
65
67
  function render(state) {
66
68
  last = state;
69
+ // What the wallet added to transactions that landed (its own checks): they were sent as it signed them. Said
70
+ // whenever a state arrives, so one that landed in a batch that then stopped is said too.
71
+ const addedLines = state.walletAdded ?? [];
72
+ for (const line of addedLines.slice(addedSaid))
73
+ log(`${line} It was sent as your wallet signed it: everything listed here is in it, untouched.`);
74
+ addedSaid = Math.max(addedSaid, addedLines.length);
67
75
  // A token's name the spec changes and the chain can't take (`unwritten`): said whether or not anything else was
68
76
  // sent. With nothing else to send, the page never calls that a landing.
69
77
  const unwritten = state.unwritten ?? [];
@@ -2,8 +2,9 @@
2
2
  // wallet, served only when asked for (`xsolana approve --keypair`, js/approve/with-key.mjs, drives this same approval
3
3
  // with a key file and no page). The
4
4
  // server builds each transaction (js/economy/plan.mjs), has the fresh keys a setup makes sign their part, and sends
5
- // only what it prepared: a wallet's answer must carry the exact message prepared for it and that wallet's valid
6
- // signature. Each prepared batch answers once and expires after 90 seconds. What lands is written back into the spec
5
+ // only what it prepared and showed: a wallet's answer must carry that wallet's valid signature of the exact message
6
+ // prepared for it, or of that message with nothing but a wallet's own additions (js/approve/wallet-adds.mjs: its
7
+ // Lighthouse checks, a compute budget). Each prepared batch answers once and expires after 90 seconds. What lands is written back into the spec
7
8
  // file as it lands, so a setup cut short goes on from where it stopped. The server never sees the owner's key.
8
9
  import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
9
10
  import { chmod, mkdtemp, open as openFile, readFile, realpath, rename, rm, stat, writeFile } from 'node:fs/promises';
@@ -16,9 +17,9 @@ import { equalBytes, fromBase64, toBase64 } from '../chain/bytes.js';
16
17
  import { alreadyProcessed, checkCluster, notMainnet, rpcClient } from '../chain/rpc.js';
17
18
  import { addressBytes, encodeBase58 } from '../chain/base58.js';
18
19
  import { slotHashes, tableAddress } from '../chain/lookup.js';
19
- import { COMPUTE_BUDGET_PROGRAM, SYSVAR_SLOT_HASHES } from '../chain/pda.js';
20
+ import { SYSVAR_SLOT_HASHES } from '../chain/pda.js';
20
21
  import { MINT_BYTES } from '../chain/token.js';
21
- import { compileMessage, readMessage, readTransaction, wireTransaction } from '../chain/tx.js';
22
+ import { compileMessage, readTransaction, wireTransaction } from '../chain/tx.js';
22
23
  import { accountKind, decode } from '../client/accounts.js';
23
24
  import { LOOT_PROGRAM, pda } from '../client/pda.js';
24
25
  import { check, describe } from '../economy/check.mjs';
@@ -30,6 +31,7 @@ import { madeSoFar, plan, PER_APPROVAL } from '../economy/plan.mjs';
30
31
  import { compareDetails, liveStepDetails, namesOf, outsideAddresses, stepDetails } from './details.mjs';
31
32
  import { freshKeypair, verifySignature } from './ed25519.mjs';
32
33
  import { checkLive, checkMints, LiveMismatch, MintMismatch, readRenames } from './live-check.mjs';
34
+ import { walletAdded } from './wallet-adds.mjs';
33
35
 
34
36
  const PREPARE_TTL_MS = 90_000;
35
37
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
@@ -150,6 +152,8 @@ export async function createApproval({
150
152
  // Goes up each time the live section changes: a batch prepared before a change is never sent.
151
153
  let generation = 0;
152
154
  const signatures = [];
155
+ // What a wallet added to transactions this session sent (its own checks, a compute budget): said on the page.
156
+ const walletAdds = [];
153
157
  const at = now();
154
158
  const mode = !spec.live ? 'new' : spec.live.partial ? 'resume' : 'change';
155
159
  /**
@@ -413,6 +417,8 @@ export async function createApproval({
413
417
  steps,
414
418
  landed: [...done].map(id => ({ id, says: said.get(id)?.says ?? id, details: said.get(id)?.details ?? [] })),
415
419
  signatures: [...signatures],
420
+ // (what a wallet added to transactions it signed, in words: they were sent as it signed them)
421
+ walletAdded: [...walletAdds],
416
422
  // The project the setup sends to, once a wallet is connected: always that wallet's own (or the one it is the
417
423
  // authority of).
418
424
  project: extra.project ?? null,
@@ -597,7 +603,7 @@ export async function createApproval({
597
603
  prepared = null;
598
604
  if (batch.generation !== generation)
599
605
  throw new ApproveError(409, 'Something landed after that batch was built: ask for the next one.');
600
- const wires = checkSigned(batch.wires, signed, wallet);
606
+ const { wires, added } = checkSigned(batch.wires, signed, wallet);
601
607
  busy = true;
602
608
  try {
603
609
  for (const [i, wire] of wires.entries()) {
@@ -654,6 +660,8 @@ export async function createApproval({
654
660
  }
655
661
  }
656
662
  signatures.push(signature);
663
+ // (what the wallet added to it is said once it has landed, never of one that did not)
664
+ if (added[i]) walletAdds.push(added[i]);
657
665
  await landed(sent, batch.ctx, signature, { more: i < wires.length - 1 });
658
666
  }
659
667
  } finally {
@@ -928,53 +936,18 @@ const sameText = (a, b) => {
928
936
  return x.length === y.length && timingSafeEqual(x, y);
929
937
  };
930
938
 
931
- /** What a Compute Budget instruction asks for, by its first byte. */
932
- const BUDGET_ASKS = {
933
- 1: 'a heap size',
934
- 2: 'a compute unit limit',
935
- 3: 'a compute unit price (a priority fee of its own)',
936
- 4: 'a limit on the account data loaded',
937
- };
938
-
939
939
  /**
940
- * What a wallet did to a message it was to sign as it stood, in words for the refusal: " (it added …)" when the
941
- * message it signed is the prepared one with instructions added (a wallet that adds its own priority fee, or checks
942
- * of its own), " (it put another blockhash on it)", or '' when the change is anything else or can't be read.
940
+ * The wallet's signed copies, as they are sent: { wires, added }. Each is the exact prepared message with the wallet's
941
+ * valid signature, or, from a wallet that adds to what it signs, the wallet's own message with its signature, taken
942
+ * only when everything prepared is in it untouched and what it added is a wallet's own (`walletAdded`,
943
+ * js/approve/wallet-adds.mjs: instructions of the Lighthouse program, a heap size). `added`: for each transaction, what
944
+ * was added to it in words, or null. Anything else is refused, and nothing is sent.
943
945
  */
944
- export function walletChange(ours, theirs) {
945
- try {
946
- const [a, b] = [readMessage(ours), readMessage(theirs)];
947
- const told = m =>
948
- m.instructions.map(ix => ({
949
- program: m.keys[ix.programIndex],
950
- kind: ix.data[0],
951
- text: `${m.keys[ix.programIndex]} ${ix.accountIndexes.map(i => m.keys[i] ?? `#${i}`).join(',')} ${toBase64(ix.data)}`,
952
- }));
953
- const mine = told(a);
954
- const added = [];
955
- let at = 0;
956
- for (const ix of told(b)) {
957
- if (at < mine.length && ix.text === mine[at].text) at++;
958
- else added.push(ix);
959
- }
960
- if (at !== mine.length || !added.length)
961
- return a.blockhash !== b.blockhash ? ' (it put another blockhash on it)' : '';
962
- const what = added.map(ix =>
963
- ix.program === COMPUTE_BUDGET_PROGRAM
964
- ? (BUDGET_ASKS[ix.kind] ?? 'a compute budget instruction')
965
- : `an instruction of the program ${ix.program}`,
966
- );
967
- return ` (it added ${[...new Set(what)].join(', ')})`;
968
- } catch {
969
- return '';
970
- }
971
- }
972
-
973
- /** The wallet's signed copies: each the exact prepared message, each with the wallet's valid signature. */
974
946
  function checkSigned(prepared, signed, wallet) {
975
947
  if (!Array.isArray(signed) || signed.length !== prepared.length)
976
948
  throw new ApproveError(400, 'The wallet’s answer does not match the request.');
977
- return prepared.map((wire, i) => {
949
+ const added = prepared.map(() => null);
950
+ const wires = prepared.map((wire, i) => {
978
951
  let mine;
979
952
  let theirs;
980
953
  try {
@@ -983,11 +956,20 @@ function checkSigned(prepared, signed, wallet) {
983
956
  } catch {
984
957
  throw new ApproveError(400, 'The wallet returned something that is not a transaction.');
985
958
  }
986
- if (!equalBytes(mine.message, theirs.message))
987
- throw new ApproveError(
988
- 400,
989
- `The wallet changed the transaction before signing it${walletChange(mine.message, theirs.message)}, so nothing was sent: only what was prepared and shown here is ever sent. A wallet that adds to what it signs can’t approve this way: use one that signs a transaction as it stands. (A setup that has not started can be signed by a key file instead: xsolana approve --keypair. One that has started is finished by the key that started it.)`,
990
- );
959
+ if (!equalBytes(mine.message, theirs.message)) {
960
+ const taken = walletAdded(mine.message, theirs.message, wallet);
961
+ if (!taken.ok)
962
+ throw new ApproveError(
963
+ 400,
964
+ `The wallet changed the transaction before signing it (it ${taken.why}), so nothing was sent. A wallet may add its own checks (instructions of the Lighthouse program) to a transaction it alone signs; everything that was prepared and shown here must be in it untouched, and nothing else may be. (A setup that has not started can be signed by a key file instead: xsolana approve --keypair. One that has started is finished by the key that started it.)`,
965
+ );
966
+ // The wallet's own message, with the wallet's own signature: it is the transaction's one signer.
967
+ const signature = theirs.signatures[0];
968
+ if (theirs.signers[0] !== wallet || !verifySignature(wallet, theirs.message, signature))
969
+ throw new ApproveError(400, 'The wallet’s signature is missing or does not match your wallet.');
970
+ added[i] = `Transaction ${i + 1} of its batch: your wallet added ${taken.added.join(', ')}.`;
971
+ return wireTransaction(theirs.message, [signature]);
972
+ }
991
973
  const slot = mine.signers.indexOf(wallet);
992
974
  if (slot < 0) throw new ApproveError(400, 'This transaction was not prepared for that wallet.');
993
975
  const signature = theirs.signatures[slot];
@@ -997,6 +979,7 @@ function checkSigned(prepared, signed, wallet) {
997
979
  signatures[slot] = signature;
998
980
  return wireTransaction(mine.message, signatures);
999
981
  });
982
+ return { wires, added };
1000
983
  }
1001
984
 
1002
985
  // ---------------------------------------------------------------- the page
@@ -0,0 +1,180 @@
1
+ // What a wallet may add to a transaction it was asked to sign as it stood, and nothing else.
2
+ //
3
+ // The approval page prepares each transaction, shows what it does, and sends what the wallet signed. Some wallets add
4
+ // to a transaction before they sign it: Phantom adds its own checks (instructions of the Lighthouse program, which
5
+ // read accounts and fail the transaction when something is not as the wallet expected). The page takes such a
6
+ // transaction only when everything that was prepared and shown is still in it, untouched, and everything else is of
7
+ // the two programs a wallet adds for its own sake: Compute Budget and Lighthouse. Then what is sent is the wallet's
8
+ // own message with the wallet's own signature. Anything else is refused, as any change was before.
9
+ //
10
+ // The rule, each part of which is needed:
11
+ // - Both messages are legacy, with no lookup table: every account is in the message itself, so every account is
12
+ // compared by its address.
13
+ // - The wallet is the message's one signer and its fee payer, in both. A transaction a fresh key signs too (a new
14
+ // mint, a new collection) can't be changed: that key signed the prepared message.
15
+ // - The same blockhash.
16
+ // - Every prepared instruction is there, in the prepared order, with the same program, the same accounts and the
17
+ // same data.
18
+ // - Every account a prepared instruction names (its program too) is a signer or writable in the wallet's message
19
+ // exactly as it was in the prepared one. Whether an account can be written is the message's word, not one
20
+ // instruction's: an added instruction that asks to write an account of ours would make it writable for ours too.
21
+ // - Every other instruction is the Lighthouse program's, or a Compute Budget instruction that asks for a heap size
22
+ // or a limit on loaded data the prepared transaction does not set. Never a unit limit or a unit price: the fee of a
23
+ // prepared transaction is the one it was shown with.
24
+ // - Nothing added names the loader of upgradeable programs, which would let a program's own account be written.
25
+ import { equalBytes } from '../chain/bytes.js';
26
+ import { COMPUTE_BUDGET_PROGRAM, LIGHTHOUSE_PROGRAM } from '../chain/pda.js';
27
+ import { readMessage } from '../chain/tx.js';
28
+
29
+ /** What a Compute Budget instruction asks for, by its first byte. */
30
+ const BUDGET_ASKS = {
31
+ 1: 'a heap size',
32
+ 2: 'a compute unit limit',
33
+ 3: 'a compute unit price (a priority fee of its own)',
34
+ 4: 'a limit on the account data loaded',
35
+ };
36
+
37
+ /** The kinds that set a transaction's fee: its unit limit and its unit price. */
38
+ const FEE_KINDS = new Set([2, 3]);
39
+ /** The loader of upgradeable programs: named anywhere in a transaction, it lets a program's own account be written. */
40
+ const UPGRADEABLE_LOADER = 'BPFLoaderUpgradeab1e11111111111111111111111';
41
+
42
+ /** A refusal of a wallet's message: `why`, in words that go after "it …". */
43
+ class Refused extends Error {}
44
+
45
+ /**
46
+ * A legacy message's accounts and instructions with nothing left as a number:
47
+ * { blockhash, signers, flags (address → { signer, writable }), instructions: [{ program, accounts: [address], data }] }.
48
+ * Throws `Refused` for a message that is not legacy, that names an account twice, or whose own numbers do not add up.
49
+ */
50
+ function laidOut(message) {
51
+ let m;
52
+ try {
53
+ m = readMessage(message);
54
+ } catch {
55
+ throw new Refused('returned something that is not a transaction');
56
+ }
57
+ if (m.version !== 'legacy' || m.lookups.length)
58
+ throw new Refused('returned a transaction that loads accounts from a lookup table, which can’t be compared');
59
+ const { numRequiredSignatures: signed, numReadonlySigned, numReadonlyUnsigned } = m.header;
60
+ const count = m.keys.length;
61
+ if (signed < 1 || signed > count || numReadonlySigned >= signed || numReadonlyUnsigned > count - signed)
62
+ throw new Refused('returned a transaction whose header does not add up');
63
+ if (new Set(m.keys).size !== count) throw new Refused('returned a transaction that names an account twice');
64
+ const flags = new Map(
65
+ m.keys.map((address, i) => [
66
+ address,
67
+ {
68
+ signer: i < signed,
69
+ writable: i < signed - numReadonlySigned || (i >= signed && i < count - numReadonlyUnsigned),
70
+ },
71
+ ]),
72
+ );
73
+ const at = i => {
74
+ if (i >= count) throw new Refused('returned a transaction that names an account it does not list');
75
+ return m.keys[i];
76
+ };
77
+ return {
78
+ blockhash: m.blockhash,
79
+ signers: m.keys.slice(0, signed),
80
+ flags,
81
+ instructions: m.instructions.map(ix => ({
82
+ program: at(ix.programIndex),
83
+ accounts: ix.accountIndexes.map(at),
84
+ data: ix.data,
85
+ })),
86
+ };
87
+ }
88
+
89
+ const sameInstruction = (a, b) =>
90
+ a.program === b.program &&
91
+ a.accounts.length === b.accounts.length &&
92
+ a.accounts.every((address, i) => address === b.accounts[i]) &&
93
+ equalBytes(a.data, b.data);
94
+
95
+ /**
96
+ * Whether `theirs`, the message a wallet signed, is `ours`, the message prepared for it, with nothing but a wallet's
97
+ * own additions. `wallet`: the address the transaction was prepared for.
98
+ * → { ok: true, added: [what it added, in words] } or { ok: false, why: what it did, in words after "it …" }.
99
+ * Two messages that are the same bytes are not this function's to judge (nothing was added).
100
+ */
101
+ export function walletAdded(ours, theirs, wallet) {
102
+ try {
103
+ // (the prepared message is this package's own: one this can't lay out is one a wallet may not change)
104
+ let a;
105
+ try {
106
+ a = laidOut(ours);
107
+ } catch {
108
+ throw new Refused('changed a transaction that can only be sent as it was prepared');
109
+ }
110
+ const b = laidOut(theirs);
111
+ if (a.signers.length !== 1 || a.signers[0] !== wallet)
112
+ throw new Refused(
113
+ 'changed a transaction that another key signs too (a new account’s own key signed it as it was prepared)',
114
+ );
115
+ if (b.signers.length !== 1) throw new Refused('added a signer');
116
+ if (b.signers[0] !== wallet) throw new Refused('put another fee payer on it');
117
+ if (a.blockhash !== b.blockhash) throw new Refused('put another blockhash on it');
118
+
119
+ // Every prepared instruction, in its order; whatever stands between them is what the wallet added.
120
+ const added = [];
121
+ let next = 0;
122
+ for (const ix of b.instructions) {
123
+ if (next < a.instructions.length && sameInstruction(ix, a.instructions[next])) next++;
124
+ else added.push(ix);
125
+ }
126
+ if (next !== a.instructions.length) throw new Refused('changed, dropped or moved one of the prepared instructions');
127
+ if (!added.length) throw new Refused('changed the transaction’s accounts');
128
+
129
+ // No account of ours is more, or less, than it was: a signer, or writable.
130
+ const mine = new Set(a.instructions.flatMap(ix => [ix.program, ...ix.accounts]));
131
+ for (const address of mine) {
132
+ const was = a.flags.get(address);
133
+ const is = b.flags.get(address);
134
+ if (!is || was.signer !== is.signer || was.writable !== is.writable)
135
+ throw new Refused(
136
+ `changed what the account ${address} is in the transaction (${was.writable ? 'it could be written, and no longer can' : 'it could only be read, and can now be written'})`,
137
+ );
138
+ }
139
+
140
+ // The network lets a program's own account be written only in a transaction that names the upgradeable loader:
141
+ // without it, an account that is called as a program is read-only whatever the message says. So the loader, named
142
+ // by anything added, would change what a prepared instruction can write. It is never the wallet's to add.
143
+ if (b.flags.has(UPGRADEABLE_LOADER) && !a.flags.has(UPGRADEABLE_LOADER))
144
+ throw new Refused('named the upgradeable loader, which the prepared transaction does not');
145
+
146
+ // What it added is a wallet's own: instructions of the Lighthouse program, or a heap size or a limit on loaded
147
+ // data the transaction did not set. Never a unit limit or a unit price: a prepared transaction's fee is its own
148
+ // (this package's setups set both), and a wallet's price on top of it is not taken.
149
+ const set = new Set(
150
+ a.instructions.filter(ix => ix.program === COMPUTE_BUDGET_PROGRAM).map(ix => (ix.data.length ? ix.data[0] : -1)),
151
+ );
152
+ const words = [];
153
+ let own = 0;
154
+ for (const ix of added) {
155
+ if (ix.program === LIGHTHOUSE_PROGRAM) {
156
+ own++;
157
+ continue;
158
+ }
159
+ if (ix.program !== COMPUTE_BUDGET_PROGRAM) throw new Refused(`added an instruction of the program ${ix.program}`);
160
+ const kind = ix.data.length ? ix.data[0] : -1;
161
+ const what = BUDGET_ASKS[kind];
162
+ if (!what) throw new Refused('added a compute budget instruction of a kind not known here');
163
+ if (ix.accounts.length)
164
+ throw new Refused('added a compute budget instruction that names accounts, which none does');
165
+ if (set.has(kind))
166
+ throw new Refused(
167
+ `added ${what.replace(/^a /, 'a second ')}: the transaction sets its own, and the network refuses one that sets it twice`,
168
+ );
169
+ if (FEE_KINDS.has(kind))
170
+ throw new Refused(`added ${what}: the fee of a transaction prepared here is the one it was shown with`);
171
+ set.add(kind);
172
+ words.push(what);
173
+ }
174
+ if (own) words.push(`${own} instruction${own === 1 ? '' : 's'} of the Lighthouse program (a wallet’s own checks)`);
175
+ return { ok: true, added: words };
176
+ } catch (e) {
177
+ if (!(e instanceof Refused)) throw e;
178
+ return { ok: false, why: e.message };
179
+ }
180
+ }
package/js/chain/pda.js CHANGED
@@ -10,6 +10,12 @@ export const TOKEN_PROGRAM = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
10
10
  export const TOKEN_2022_PROGRAM = 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';
11
11
  export const ASSOCIATED_TOKEN_PROGRAM = 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL';
12
12
  export const COMPUTE_BUDGET_PROGRAM = 'ComputeBudget111111111111111111111111111111';
13
+ /**
14
+ * Lighthouse: a program of assertions (it reads accounts and fails the transaction when one is not as expected),
15
+ * which a wallet may add to a transaction before signing it (Phantom does). The same address on mainnet and devnet:
16
+ * its source's `declare_id!` and its README, github.com/Jac0xb/lighthouse.
17
+ */
18
+ export const LIGHTHOUSE_PROGRAM = 'L2TExMFKdjpN9kozasaurPirfHy9P8sbXoAN1qA3S95';
13
19
  export const METAPLEX_METADATA_PROGRAM = 'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s';
14
20
  /** Metaplex Core: the program of Core assets and collections. */
15
21
  export const MPL_CORE_PROGRAM = 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d';
@@ -1892,8 +1892,8 @@ export const SETUP_UNIT_PRICE = 1n;
1892
1892
  /**
1893
1893
  * A setup transaction's instructions as the owner's wallet is handed them: led by a compute unit limit and a compute
1894
1894
  * unit price, each once. A wallet that adds its own priority fee (Phantom does) adds it only to a transaction that
1895
- * sets neither, and the approval page sends a transaction only as it prepared it, so every setup transaction sets both
1896
- * itself. The limit is all a transaction can have (1,400,000 units): nothing here measures what a step takes, a box's
1895
+ * sets neither, and the approval page takes no price a wallet puts on a transaction that has one, so every setup
1896
+ * transaction sets both itself: its fee is the plan's own, whatever the wallet. The limit is all a transaction can have (1,400,000 units): nothing here measures what a step takes, a box's
1897
1897
  * write needs it, and no step may fail for want of compute. A limit or a price the instructions already carry is
1898
1898
  * kept.
1899
1899
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xgamesfun/solana",
3
- "version": "0.28.0",
3
+ "version": "0.28.1",
4
4
  "mcpName": "io.github.XGamesFun/solana-kit",
5
5
  "description": "Loot boxes, gacha, shops, lotteries, auctions, NFT staking, crafting and rewards on Solana without writing a smart contract: one shared program, its JS client, a CLI, and an MCP server and skill so Claude Code, Codex or any AI agent can design, check, simulate and launch an economy.",
6
6
  "license": "Apache-2.0",
package/release.json CHANGED
@@ -4,5 +4,5 @@
4
4
  "sbfBytes": 1794616,
5
5
  "sbfSha256": "ac8b09ed2c3a3f7daa2dfab55d2f6dea19fa43e4c98b44a82da2a68a7bc8ebbe",
6
6
  "idlSha256": "ef3b8043d62c606e850df75773b4535cdb08206216e17aca653111a77403abd7",
7
- "clientSha256": "166349fb1609e0a5e051eba44691783ea1ffaf6f307983a7c24c4c3f0a44f60e"
7
+ "clientSha256": "3ef11453089e952e50d9d5a7199d9629b3417a091a071ea6dfd90f42746a2421"
8
8
  }
@@ -10,9 +10,9 @@ writes or deploys a contract: the spec is checked, simulated and priced by the `
10
10
  signed by a key file on the owner's own machine, once they have read what will be sent and said yes. It runs on
11
11
  **devnet** until the program is audited.
12
12
 
13
- The tools are the `xsolana_*` MCP tools. Without MCP, the same from a terminal: `npx -y @xgamesfun/solana@0.28.0 <guide|examples|check|simulate|cost|explain|review|publish|status> [xsolana.json]`,
14
- `npx -y @xgamesfun/solana@0.28.0 token <mint>` for a coin made elsewhere,
15
- or `npx -y @xgamesfun/solana@0.28.0 tool <name> '<json>'` for any tool (always with the exact version).
13
+ The tools are the `xsolana_*` MCP tools. Without MCP, the same from a terminal: `npx -y @xgamesfun/solana@0.28.1 <guide|examples|check|simulate|cost|explain|review|publish|status> [xsolana.json]`,
14
+ `npx -y @xgamesfun/solana@0.28.1 token <mint>` for a coin made elsewhere,
15
+ or `npx -y @xgamesfun/solana@0.28.1 tool <name> '<json>'` for any tool (always with the exact version).
16
16
 
17
17
  ## Rules (always)
18
18
 
@@ -137,7 +137,7 @@ or `npx -y @xgamesfun/solana@0.28.0 tool <name> '<json>'` for any tool (always w
137
137
  9. **When they say "put it live"**: `xsolana_publish` lists every step and its cost and gives the commands, which you
138
138
  run in the project folder exactly as given (they name the exact version; never drop it):
139
139
  - **A new economy gets a key file**, unless the project has one:
140
- `npx -y @xgamesfun/solana@0.28.0 keygen ~/.config/xsolana/<name>.json`. Give the person the address it prints. A
140
+ `npx -y @xgamesfun/solana@0.28.1 keygen ~/.config/xsolana/<name>.json`. Give the person the address it prints. A
141
141
  Solana key file they already have and name themselves (`~/.config/solana/id.json`) works the same; never go
142
142
  looking for one.
143
143
  - **Say what the key file is** before anything is sent: the project's owner, and, unless the spec says otherwise,
@@ -152,7 +152,7 @@ or `npx -y @xgamesfun/solana@0.28.0 tool <name> '<json>'` for any tool (always w
152
152
  (`xsolana_cost`), any seed in a coin that already exists, and every NFT a stash, a raffle or an auction names. A
153
153
  collection they already have needs the key's address as its update authority. If they would rather not move
154
154
  those, offer the browser wallet.
155
- - **Show it**: `npx -y @xgamesfun/solana@0.28.0 approve xsolana.json --keypair <key file>` prints every step with
155
+ - **Show it**: `npx -y @xgamesfun/solana@0.28.1 approve xsolana.json --keypair <key file>` prints every step with
156
156
  what it pays and trusts, the warnings, the notice and a code, and sends nothing. Give the person that printout as
157
157
  it is, not a summary of it, and ask.
158
158
  - **On their yes**, the same command with `--yes <code>`. What lands is written back into `xsolana.json`. If it
@@ -3,6 +3,12 @@ export declare const TOKEN_PROGRAM = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5D
3
3
  export declare const TOKEN_2022_PROGRAM = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb";
4
4
  export declare const ASSOCIATED_TOKEN_PROGRAM = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
5
5
  export declare const COMPUTE_BUDGET_PROGRAM = "ComputeBudget111111111111111111111111111111";
6
+ /**
7
+ * Lighthouse: a program of assertions (it reads accounts and fails the transaction when one is not as expected),
8
+ * which a wallet may add to a transaction before signing it (Phantom does). The same address on mainnet and devnet:
9
+ * its source's `declare_id!` and its README, github.com/Jac0xb/lighthouse.
10
+ */
11
+ export declare const LIGHTHOUSE_PROGRAM = "L2TExMFKdjpN9kozasaurPirfHy9P8sbXoAN1qA3S95";
6
12
  export declare const METAPLEX_METADATA_PROGRAM = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s";
7
13
  /** Metaplex Core: the program of Core assets and collections. */
8
14
  export declare const MPL_CORE_PROGRAM = "CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d";
@@ -1051,8 +1051,8 @@ export declare const SETUP_UNIT_PRICE = 1n;
1051
1051
  /**
1052
1052
  * A setup transaction's instructions as the owner's wallet is handed them: led by a compute unit limit and a compute
1053
1053
  * unit price, each once. A wallet that adds its own priority fee (Phantom does) adds it only to a transaction that
1054
- * sets neither, and the approval page sends a transaction only as it prepared it, so every setup transaction sets both
1055
- * itself. The limit is all a transaction can have (1,400,000 units): nothing here measures what a step takes, a box's
1054
+ * sets neither, and the approval page takes no price a wallet puts on a transaction that has one, so every setup
1055
+ * transaction sets both itself: its fee is the plan's own, whatever the wallet. The limit is all a transaction can have (1,400,000 units): nothing here measures what a step takes, a box's
1056
1056
  * write needs it, and no step may fail for want of compute. A limit or a price the instructions already carry is
1057
1057
  * kept.
1058
1058
  */