@xgamesfun/solana 0.26.0 → 0.26.2
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 +8 -8
- package/js/chain/metadata.js +76 -0
- package/js/client/idl/loot.js +1 -1
- package/js/client/idl/loot.json +4 -3
- package/js/client/sizes.js +180 -66
- package/js/economy/check.mjs +64 -19
- package/js/economy/cost.mjs +17 -3
- package/js/economy/explain.mjs +14 -0
- package/js/economy/guide.mjs +2 -2
- package/js/economy/plan.mjs +62 -12
- package/js/economy/schema.json +4 -3
- package/js/play/index.js +15 -7
- package/package.json +1 -1
- package/release.json +5 -5
- package/skills/xsolana-economy/SKILL.md +3 -3
- package/skills/xsolana-economy/references/telling-the-person.md +9 -1
- package/types/chain/metadata.d.ts +40 -0
- package/types/client/sizes.d.ts +42 -8
- package/types/economy/check.d.mts +6 -2
- package/types/economy/cost.d.mts +3 -0
- package/types/economy/live.d.mts +2 -0
- package/types/economy/plan.d.mts +23 -15
package/js/economy/plan.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// The setup plan: every instruction that puts an economy live on loot, in order, built with the client and
|
|
2
2
|
// packed into transactions that fit. Nothing here signs or talks to the chain. Pure.
|
|
3
3
|
//
|
|
4
|
-
// A step is { id, part, says, signers, creates: [{ what, bytes }], build(ctx) → instructions }
|
|
4
|
+
// A step is { id, part, says, signers, creates: [{ what, bytes, fee? }], build(ctx) → instructions } (`fee`: lamports
|
|
5
|
+
// the program that makes the account keeps in it, on top of its rent: Token Metadata's). `ctx` names the
|
|
5
6
|
// addresses a step needs: { owner, authority, project, collection, tree, treasury, mints: { tokenKey: mint }, keys: {
|
|
6
7
|
// name: address } } where `keys` are the fresh keys this setup makes ("new:collection", "new:tree", "new:mint/points"), each of
|
|
7
8
|
// which signs the step that creates it. `placeholderContext` fills every address with a stand-in, so a plan can be
|
|
@@ -20,8 +21,14 @@ import {
|
|
|
20
21
|
TOKEN_PROGRAM,
|
|
21
22
|
} from '../chain/pda.js';
|
|
22
23
|
import { createTableIx, extendTableIx } from '../chain/lookup.js';
|
|
24
|
+
import { createFungibleMetadataIx, METADATA_BYTES, METADATA_FEE, METADATA_LIMITS } from '../chain/metadata.js';
|
|
23
25
|
import { MEMO_PROGRAM } from '../chain/token2022.js';
|
|
24
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
computeUnitLimit,
|
|
28
|
+
createAssociatedTokenAccountIdempotent,
|
|
29
|
+
syncNative,
|
|
30
|
+
transferLamports,
|
|
31
|
+
} from '../chain/programs.js';
|
|
25
32
|
import {
|
|
26
33
|
createAccountIx,
|
|
27
34
|
initializeMint2Ix,
|
|
@@ -34,7 +41,7 @@ import { compileMessage, transactionSize } from '../chain/tx.js';
|
|
|
34
41
|
import { sha256 } from '../chain/sha256.js';
|
|
35
42
|
import { utf8 } from '../chain/bytes.js';
|
|
36
43
|
import * as loot from '../client/index.js';
|
|
37
|
-
import { check } from './check.mjs';
|
|
44
|
+
import { check, MAX_COMPUTE_UNITS } from './check.mjs';
|
|
38
45
|
import {
|
|
39
46
|
ACCOUNT_OVERHEAD,
|
|
40
47
|
boxBuys,
|
|
@@ -727,18 +734,40 @@ export function setupSteps(economy, { now = 0, only = null, live: given = null }
|
|
|
727
734
|
const fresh = !live?.project;
|
|
728
735
|
|
|
729
736
|
// Tokens the project mints: a classic mint with the owner as its authority, handed to the project right after.
|
|
737
|
+
// Each is named on chain in the same step (its Token Metadata record: the name, the ticker and the link wallets
|
|
738
|
+
// show), while the owner is still its mint authority: Token Metadata takes a record from nobody else, so a mint whose
|
|
739
|
+
// minting was handed on or turned off without one can never have one. The owner stays the record's update authority.
|
|
730
740
|
for (const t of m.tokens.values()) {
|
|
731
741
|
if (t.source !== 'game' || live?.mints?.[t.key] || (t.mint && !t.mint.startsWith('@'))) continue;
|
|
732
742
|
const key = `new:mint/${t.key}`;
|
|
743
|
+
const named = `write its name on chain (“${t.name}”, ${t.symbol}, ${t.uri ? `with the link ${t.uri}` : 'no picture link'}: only your wallet can change it later, which these tools don’t do yet, and Token Metadata keeps ${Number(METADATA_FEE) / 1e9} SOL for it)`;
|
|
744
|
+
const record = { what: `the name of ${t.symbol} on chain`, bytes: METADATA_BYTES, fee: Number(METADATA_FEE) };
|
|
745
|
+
// The checker refuses a name, ticker or link too long for a record before any plan is made, except on a token
|
|
746
|
+
// that is live already, whose words are only labels. Such a token is made here only when a live economy is
|
|
747
|
+
// planned as if it were new (explain's cost of the whole setup, which is never sent): its record is left out.
|
|
748
|
+
const fits = ['name', 'symbol', 'uri'].every(f => utf8(String(t[f] ?? '')).length <= METADATA_LIMITS[f]);
|
|
749
|
+
const name = ctx =>
|
|
750
|
+
fits
|
|
751
|
+
? [
|
|
752
|
+
createFungibleMetadataIx({
|
|
753
|
+
mint: ctx.keys[key],
|
|
754
|
+
authority: ctx.owner,
|
|
755
|
+
name: t.name,
|
|
756
|
+
symbol: t.symbol,
|
|
757
|
+
uri: t.uri ?? '',
|
|
758
|
+
}),
|
|
759
|
+
]
|
|
760
|
+
: [];
|
|
733
761
|
if (t.fixedSupply) {
|
|
734
762
|
const supply = amount(t.supply, t);
|
|
735
763
|
add({
|
|
736
764
|
id: `token:${t.key}`,
|
|
737
765
|
part: `token:${t.key}`,
|
|
738
|
-
says: `Make the token ${t.symbol} (${t.decimals} decimals) with a fixed supply of ${t.supply}, all of it to your wallet, then turn its minting off for good`,
|
|
766
|
+
says: `Make the token ${t.symbol} (${t.decimals} decimals) with a fixed supply of ${t.supply}, all of it to your wallet, ${named}, then turn its minting off for good`,
|
|
739
767
|
signers: [owner, key],
|
|
740
768
|
creates: [
|
|
741
769
|
{ what: `the mint of ${t.symbol}`, bytes: MINT_BYTES },
|
|
770
|
+
...(fits ? [record] : []),
|
|
742
771
|
{ what: `your ${t.symbol} account`, bytes: 165 },
|
|
743
772
|
],
|
|
744
773
|
build: ctx => [
|
|
@@ -750,6 +779,7 @@ export function setupSteps(economy, { now = 0, only = null, live: given = null }
|
|
|
750
779
|
owner: TOKEN_PROGRAM,
|
|
751
780
|
}),
|
|
752
781
|
initializeMint2Ix({ mint: ctx.keys[key], decimals: t.decimals, authority: ctx.owner }),
|
|
782
|
+
...name(ctx),
|
|
753
783
|
createAssociatedTokenAccountIdempotent(ctx.owner, ctx.owner, ctx.keys[key]),
|
|
754
784
|
mintToIx({
|
|
755
785
|
mint: ctx.keys[key],
|
|
@@ -765,9 +795,9 @@ export function setupSteps(economy, { now = 0, only = null, live: given = null }
|
|
|
765
795
|
add({
|
|
766
796
|
id: `token:${t.key}`,
|
|
767
797
|
part: `token:${t.key}`,
|
|
768
|
-
says: `Make the token ${t.symbol} (${t.decimals} decimals), then hand its minting to the project`,
|
|
798
|
+
says: `Make the token ${t.symbol} (${t.decimals} decimals), ${named}, then hand its minting to the project`,
|
|
769
799
|
signers: [owner, key],
|
|
770
|
-
creates: [{ what: `the mint of ${t.symbol}`, bytes: MINT_BYTES }],
|
|
800
|
+
creates: [{ what: `the mint of ${t.symbol}`, bytes: MINT_BYTES }, ...(fits ? [record] : [])],
|
|
771
801
|
build: ctx => [
|
|
772
802
|
createAccountIx({
|
|
773
803
|
from: ctx.owner,
|
|
@@ -777,6 +807,7 @@ export function setupSteps(economy, { now = 0, only = null, live: given = null }
|
|
|
777
807
|
owner: TOKEN_PROGRAM,
|
|
778
808
|
}),
|
|
779
809
|
initializeMint2Ix({ mint: ctx.keys[key], decimals: t.decimals, authority: ctx.owner }),
|
|
810
|
+
...name(ctx),
|
|
780
811
|
setAuthorityIx({ account: ctx.keys[key], current: ctx.owner, newAuthority: ctx.project }),
|
|
781
812
|
],
|
|
782
813
|
});
|
|
@@ -944,6 +975,10 @@ export function setupSteps(economy, { now = 0, only = null, live: given = null }
|
|
|
944
975
|
id: `${part}:${kind}`,
|
|
945
976
|
part,
|
|
946
977
|
says,
|
|
978
|
+
// A box's write finds each of its pools' and stashes' addresses (1,500 units a try) and compares its entries
|
|
979
|
+
// pair by pair: a box of 16 stashes takes some 170,000 units, more where its addresses take more tries, past
|
|
980
|
+
// the 200,000 an instruction has by default. Its transaction asks for all a transaction can have.
|
|
981
|
+
compute: Boolean(def.Box),
|
|
947
982
|
creates: [{ what, bytes }, ...pot],
|
|
948
983
|
build: ctx => [
|
|
949
984
|
loot.upsertIx({ owner: ctx.owner, project: project(ctx), def: resolve(def, ctx), ...options(ctx) }),
|
|
@@ -1785,7 +1820,18 @@ export function setupContext(m, { owner: ownerKey, keys, live: given = {}, rent
|
|
|
1785
1820
|
export const stepBytes = (instructions, ctx) =>
|
|
1786
1821
|
transactionSize(compileMessage({ payer: ctx.owner, blockhash: placeholderKey('blockhash'), instructions }));
|
|
1787
1822
|
|
|
1788
|
-
/**
|
|
1823
|
+
/** `instructions`, led by a compute limit of all a transaction can have when `compute` (a box's write is in them). */
|
|
1824
|
+
const budgeted = (instructions, compute) =>
|
|
1825
|
+
compute ? [computeUnitLimit(MAX_COMPUTE_UNITS), ...instructions] : instructions;
|
|
1826
|
+
|
|
1827
|
+
/** The bytes of a transaction of `step` alone, as `pack` sends it (its compute limit included). */
|
|
1828
|
+
export const stepAloneBytes = (step, ctx) => stepBytes(budgeted(step.build(ctx), Boolean(step.compute)), ctx);
|
|
1829
|
+
|
|
1830
|
+
/**
|
|
1831
|
+
* Packs steps into transactions under the size budget, one signer set at a time; each step whole. A transaction that
|
|
1832
|
+
* carries a step needing more compute than an instruction has by default (`compute`: a box's write) asks for all a
|
|
1833
|
+
* transaction can have, first.
|
|
1834
|
+
*/
|
|
1789
1835
|
export function pack(steps, ctx) {
|
|
1790
1836
|
const transactions = [];
|
|
1791
1837
|
let current = null;
|
|
@@ -1795,25 +1841,28 @@ export function pack(steps, ctx) {
|
|
|
1795
1841
|
// A step with nothing to send (the lookup table of a plan built with no recent slot) is left out.
|
|
1796
1842
|
if (!instructions.length) continue;
|
|
1797
1843
|
const signerKind = step.signers.includes('authority') ? 'authority' : 'owner';
|
|
1798
|
-
// A write that names boxes to roll takes all the compute a transaction has: alone.
|
|
1799
1844
|
if (current && current.signer === signerKind && !step.alone && !current.alone) {
|
|
1800
|
-
const
|
|
1845
|
+
const compute = current.compute || Boolean(step.compute);
|
|
1846
|
+
const next = budgeted([...current.built.flat(), ...instructions], compute);
|
|
1801
1847
|
if (sizeOf(next) <= BUDGET) {
|
|
1802
1848
|
current.instructions = next;
|
|
1849
|
+
current.compute = compute;
|
|
1803
1850
|
current.steps.push(step);
|
|
1804
1851
|
current.built.push(instructions);
|
|
1805
1852
|
current.signers = [...new Set([...current.signers, ...step.signers])];
|
|
1806
1853
|
continue;
|
|
1807
1854
|
}
|
|
1808
1855
|
}
|
|
1809
|
-
const
|
|
1856
|
+
const alone = budgeted(instructions, Boolean(step.compute));
|
|
1857
|
+
const size = sizeOf(alone);
|
|
1810
1858
|
if (size > BUDGET) throw new Error(`The step “${step.says}” is ${size} bytes: too big for one transaction.`);
|
|
1811
1859
|
// `built`: each step's own instructions, in the order of `steps`.
|
|
1812
1860
|
current = {
|
|
1813
1861
|
signer: signerKind,
|
|
1814
1862
|
steps: [step],
|
|
1815
1863
|
built: [instructions],
|
|
1816
|
-
instructions,
|
|
1864
|
+
instructions: alone,
|
|
1865
|
+
compute: Boolean(step.compute),
|
|
1817
1866
|
signers: [...step.signers],
|
|
1818
1867
|
alone: Boolean(step.alone),
|
|
1819
1868
|
};
|
|
@@ -1828,7 +1877,8 @@ export function pack(steps, ctx) {
|
|
|
1828
1877
|
* real instructions; without it they are built on stand-in addresses (for sizing, cost and showing).
|
|
1829
1878
|
*/
|
|
1830
1879
|
export function plan(economy, { now = Math.floor(Date.now() / 1000), ctx = null, only = null, live = null } = {}) {
|
|
1831
|
-
|
|
1880
|
+
// With a real context the project is known: a box's write is checked with its own address searches counted.
|
|
1881
|
+
const checked = check(economy, { now, ...(ctx?.project ? { project: ctx.project } : {}) });
|
|
1832
1882
|
if (!checked.ok) return { ok: false, refusals: checked.refusals, warnings: checked.warnings };
|
|
1833
1883
|
const { steps, model: m } = setupSteps(economy, { now, only, live });
|
|
1834
1884
|
if (ctx)
|
package/js/economy/schema.json
CHANGED
|
@@ -363,7 +363,7 @@
|
|
|
363
363
|
"type": "object",
|
|
364
364
|
"required": ["key", "source"],
|
|
365
365
|
"additionalProperties": false,
|
|
366
|
-
"description": "A currency. `sol`: SOL (players pay in wrapped SOL). `existing`: any mint by address, launched anywhere, classic or Token-2022, pump.fun included (xgames.fun takes no share of its fees). `project` (`game` is its older spelling): a token made when the economy goes live: the project mints it as its rules say (points, tickets, shards), or, with `supply`, a fixed supply minted once to the owner (a community token you keep).",
|
|
366
|
+
"description": "A currency. `sol`: SOL (players pay in wrapped SOL). `existing`: any mint by address, launched anywhere, classic or Token-2022, pump.fun included (xgames.fun takes no share of its fees). `project` (`game` is its older spelling): a token made when the economy goes live: the project mints it as its rules say (points, tickets, shards), or, with `supply`, a fixed supply minted once to the owner (a community token you keep). A token made this way is named on chain as it is made (its `name`, `symbol` and `uri`), which is what wallets show. Only the owner's wallet can change that later, and a change to them in a spec that is already live changes nothing on chain yet.",
|
|
367
367
|
"properties": {
|
|
368
368
|
"key": {
|
|
369
369
|
"$ref": "#/$defs/key"
|
|
@@ -401,11 +401,12 @@
|
|
|
401
401
|
"description": "Where the token trades, recorded for the app and not read or enforced by the tools: loot doesn't launch or trade tokens. A token launched elsewhere is `existing`, by its mint. `games.fun` is the older spelling of `xgames.fun`."
|
|
402
402
|
},
|
|
403
403
|
"uri": {
|
|
404
|
-
"$ref": "#/$defs/link"
|
|
404
|
+
"$ref": "#/$defs/link",
|
|
405
|
+
"description": "`project` only: a link to a JSON file that names the token's picture (`{ \"name\", \"symbol\", \"image\": \"https://…\" }`), at most 200 bytes. Going live writes it on chain with the token's name and ticker; without it wallets show the name and ticker and no picture."
|
|
405
406
|
},
|
|
406
407
|
"image": {
|
|
407
408
|
"$ref": "#/$defs/link",
|
|
408
|
-
"description": "A picture of it, recorded for the app: the tools don't read it, and the setup doesn't write it
|
|
409
|
+
"description": "A picture of it, recorded for the app: the tools don't read it, and the setup doesn't write it on chain (what wallets show is the `image` the JSON file at `uri` names)."
|
|
409
410
|
},
|
|
410
411
|
"powers": {
|
|
411
412
|
"type": "object",
|
package/js/play/index.js
CHANGED
|
@@ -1489,6 +1489,11 @@ export function solanaApp({
|
|
|
1489
1489
|
const g = await projectNow();
|
|
1490
1490
|
const box = await read('LootBox', loot.pda.box(project, boxId));
|
|
1491
1491
|
if (!Number.isInteger(count) || count < 1) throw new Error('Buy a whole number, one at least (count).');
|
|
1492
|
+
// One `buy` prices at most so many sales (`PackTooLarge` past it): more is several purchases.
|
|
1493
|
+
if (count > loot.MOST_SALES_A_BUY)
|
|
1494
|
+
throw new Error(
|
|
1495
|
+
`One purchase buys at most ${loot.MOST_SALES_A_BUY.toLocaleString('en-US')} (count): buy the rest in another.`,
|
|
1496
|
+
);
|
|
1492
1497
|
const bound =
|
|
1493
1498
|
boundOf({ ...box, currencyKey: part?.currency }, part?.name ?? `box ${boxId}`, { maxPrice, listing }, part?.key) *
|
|
1494
1499
|
BigInt(count);
|
|
@@ -2325,8 +2330,9 @@ export function solanaApp({
|
|
|
2325
2330
|
const e = i == null ? null : r.entries[i];
|
|
2326
2331
|
const out = [];
|
|
2327
2332
|
const rolls = [];
|
|
2328
|
-
// Whether a pull here wants the largest heap (a run of rolls
|
|
2329
|
-
//
|
|
2333
|
+
// Whether a pull here wants the largest heap (a run of rolls, or several NFTs out of the round's own stash), and
|
|
2334
|
+
// whether one stops short of the part (a raffle's NFTs past what one pull takes out or one transaction carries, a
|
|
2335
|
+
// run of rolls past what one transaction carries): a next pull carries on.
|
|
2330
2336
|
let heap = false;
|
|
2331
2337
|
let partial = false;
|
|
2332
2338
|
const g = await projectNow();
|
|
@@ -2382,10 +2388,12 @@ export function solanaApp({
|
|
|
2382
2388
|
projectMint: mintOf(),
|
|
2383
2389
|
};
|
|
2384
2390
|
more.nonce = pullNonce(key, i);
|
|
2385
|
-
// A run of places that each give a roll
|
|
2386
|
-
//
|
|
2387
|
-
// carries the
|
|
2388
|
-
|
|
2391
|
+
// A run of places that each give a roll (two accounts a place), or several NFTs out of the round's own stash
|
|
2392
|
+
// (each NFT's accounts, and heap the program does not give back: 8 Core moves in a round of some 125 places or
|
|
2393
|
+
// more run past the default 32 KiB): as many places as one transaction carries, on the largest heap. The pull
|
|
2394
|
+
// is sized from a dry run, largest first; the next pull carries on while the round still owes the entry, and
|
|
2395
|
+
// only the last carries the close.
|
|
2396
|
+
if (given.Roll || (stash && loot.prizePlacesOwed(r, i, run).length > 1)) {
|
|
2389
2397
|
const owed = loot.prizePlacesOwed(r, i, run).length;
|
|
2390
2398
|
const sized = async most => {
|
|
2391
2399
|
const ix = loot.pullIx({
|
|
@@ -4928,7 +4936,7 @@ export function solanaApp({
|
|
|
4928
4936
|
heap ||= built.heap;
|
|
4929
4937
|
partial ||= built.partial;
|
|
4930
4938
|
}
|
|
4931
|
-
// A run of rolls asks for the largest heap.
|
|
4939
|
+
// A run of rolls, or several NFTs out of the round's own stash, asks for the largest heap.
|
|
4932
4940
|
if (heap) instructions.splice(1, 0, requestHeapFrame(loot.LARGE_SETTLE_HEAP));
|
|
4933
4941
|
// A pull that stopped short of its part (a raffle's NFTs, 64 a pull; a run of rolls): sent, then the next carries
|
|
4934
4942
|
// on while the round moves (its NFTs out, its places paid) and still owes it.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xgamesfun/solana",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.2",
|
|
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
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.26.
|
|
2
|
+
"version": "0.26.1",
|
|
3
3
|
"programId": "9cwqM1TXytPnFZsLfmUyv2dg4uW6Ch9C6UJVPns7NFQ",
|
|
4
|
-
"sbfBytes":
|
|
5
|
-
"sbfSha256": "
|
|
6
|
-
"idlSha256": "
|
|
7
|
-
"clientSha256": "
|
|
4
|
+
"sbfBytes": 1783800,
|
|
5
|
+
"sbfSha256": "af318ccfa4d2901076b85fcd1b4467b03253adf0de16e73a9dfc1ef8077b2c14",
|
|
6
|
+
"idlSha256": "0e88fc826a585cf17917cbc654e33b68232771769fe9aae1d5899a257940ff47",
|
|
7
|
+
"clientSha256": "20e40ee24d808d10091a0bd9973c3c4883e9dd35da4a6beac97557dd85a362c8"
|
|
8
8
|
}
|
|
@@ -9,8 +9,8 @@ An economy is one JSON spec (`xsolana.json` in the project) on **loot**, a Solan
|
|
|
9
9
|
writes or deploys a contract: the spec is checked, simulated and priced by the `xsolana_*` tools, and the project's
|
|
10
10
|
owner puts it live by approving in their own wallet. It runs on **devnet** until the program is audited.
|
|
11
11
|
|
|
12
|
-
The tools are the `xsolana_*` MCP tools. Without MCP, the same from a terminal: `npx -y @xgamesfun/solana@0.26.
|
|
13
|
-
or `npx -y @xgamesfun/solana@0.26.
|
|
12
|
+
The tools are the `xsolana_*` MCP tools. Without MCP, the same from a terminal: `npx -y @xgamesfun/solana@0.26.2 <guide|examples|check|simulate|cost|explain|review|publish|status> [xsolana.json]`,
|
|
13
|
+
or `npx -y @xgamesfun/solana@0.26.2 tool <name> '<json>'` for any tool (always with the exact version).
|
|
14
14
|
|
|
15
15
|
## Rules (always)
|
|
16
16
|
|
|
@@ -121,7 +121,7 @@ or `npx -y @xgamesfun/solana@0.26.0 tool <name> '<json>'` for any tool (always w
|
|
|
121
121
|
level would cost. Run it again after every change that touches a route that pays.
|
|
122
122
|
9. **When they say "put it live"**: `xsolana_publish` lists every step and its cost and gives the command. Tell them to
|
|
123
123
|
run it themselves in the project folder, exactly as `xsolana_publish` gives it (it names the exact version:
|
|
124
|
-
`npx -y @xgamesfun/solana@0.26.
|
|
124
|
+
`npx -y @xgamesfun/solana@0.26.2 approve xsolana.json`; never drop the version). It opens a page on their computer where
|
|
125
125
|
they connect a devnet wallet and approve; what lands is written back into `xsolana.json`. You publish nothing.
|
|
126
126
|
10. **After it is live**: `xsolana_status` shows each part on chain; read it before acting on what the person says
|
|
127
127
|
happened. A later change is the same loop: save, publish, and the approval sends only what changed. What only the
|
|
@@ -90,7 +90,8 @@ money; it holds no files and serves nothing.
|
|
|
90
90
|
the mints and vaults of its tokens, a fresh item's tables) are bounded by that, however many outcomes it has.
|
|
91
91
|
`xsolana_check` counts them. One answer also holds only so much of a box (measured: 128 outcomes that give
|
|
92
92
|
nothing, 29 of token amounts, 18 each of its own item; 41 slots; 72 odds rules; 16 token prizes or 18 collectibles
|
|
93
|
-
in one outcome; 340 texts in a gate
|
|
93
|
+
in one outcome; 340 texts in a gate; fewer where outcomes name pools or stashes, since the write counts finding
|
|
94
|
+
each one's address): a box past that is refused when written. Put a long weighted list in a table
|
|
94
95
|
or a stash, drawn by one outcome.
|
|
95
96
|
- **The program takes any time, and the checker says a far one's date in words (W-FAR-TIME).** A time that looks like
|
|
96
97
|
milliseconds, or lands more than three years out, locks the bids, tickets, NFTs or vault that wait on it until then.
|
|
@@ -116,6 +117,13 @@ Don'ts:
|
|
|
116
117
|
otherwise, and until it is filled those prizes pay nothing.
|
|
117
118
|
- **A token the project mints** (`source: "project"`) is how points, tickets, attempts and shards work; an existing coin
|
|
118
119
|
needs its mint address and decimals from the person (ask; never guess an address).
|
|
120
|
+
- **A token the setup makes is named on chain as it goes live**: its `name`, its `symbol` and its `uri` (a link to a
|
|
121
|
+
JSON file the project hosts, `{ "name", "symbol", "image": "https://…" }`), which is what wallets and explorers show.
|
|
122
|
+
Ask for the name, the ticker and a picture before going live: with no `uri` it shows by name and ticker with no
|
|
123
|
+
picture. Only the owner's wallet can change the three later (Token Metadata's rule, not loot's), and these tools
|
|
124
|
+
don't change a live token's yet, so get them right first. The record costs about 0.014 SOL (`xsolana_cost` says it:
|
|
125
|
+
rent, and 0.01 SOL Token Metadata keeps). A token that went live before the tools wrote one has none and
|
|
126
|
+
can't be given one: only a mint's own authority can name it, and its minting was handed on or turned off.
|
|
119
127
|
- **NFTs the project has no authority over** (any collection but its own): they count as they are. Say that
|
|
120
128
|
whatever their collection lets be written on an NFT (even by its holder) is what a gate or a boost reads, and that
|
|
121
129
|
the project's notes about one (levels, uses) live in its Sheet, which the authority writes and pays for. Every
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** A record's size: Token Metadata pads every one to it, whatever its name, ticker and link. */
|
|
2
|
+
export declare const METADATA_BYTES = 607;
|
|
3
|
+
/** What Token Metadata keeps in each record it makes, on top of the record's rent: its own fee. */
|
|
4
|
+
export declare const METADATA_FEE = 10000000n;
|
|
5
|
+
/** The longest name, ticker and link a record takes, in UTF-8 bytes. */
|
|
6
|
+
export declare const METADATA_LIMITS: Readonly<{
|
|
7
|
+
name: 32;
|
|
8
|
+
symbol: 10;
|
|
9
|
+
uri: 200;
|
|
10
|
+
}>;
|
|
11
|
+
/** The address of a mint's Token Metadata record. */
|
|
12
|
+
export declare const metadataAddress: (mint: any) => any;
|
|
13
|
+
/**
|
|
14
|
+
* Token Metadata's CreateV1 for a fungible token whose mint exists: its name, its ticker and a link to a JSON file with
|
|
15
|
+
* its picture ('' for none). `authority` is the mint's authority and signs; it becomes the record's update authority,
|
|
16
|
+
* the only one who can change the record later. `payer` pays its rent and Token Metadata's fee (`METADATA_FEE`).
|
|
17
|
+
* The mint is listed read-only: this path of Token Metadata writes nothing to a mint that exists. That is the
|
|
18
|
+
* program's behaviour, not a lock: in a setup's own transaction the mint is writable all the same (the same
|
|
19
|
+
* transaction makes it), so the tests read its supply and authorities back after the record is made. The record says
|
|
20
|
+
* a fungible token whatever the decimals (CreateMetadataAccountV3 would call one of 0 decimals an asset, which
|
|
21
|
+
* wallets list with collectibles).
|
|
22
|
+
*/
|
|
23
|
+
export declare function createFungibleMetadataIx({ mint, authority, payer, name, symbol, uri }: {
|
|
24
|
+
authority: any;
|
|
25
|
+
mint: any;
|
|
26
|
+
name: any;
|
|
27
|
+
payer?: any;
|
|
28
|
+
symbol: any;
|
|
29
|
+
uri?: string | undefined;
|
|
30
|
+
}): {
|
|
31
|
+
programId: string;
|
|
32
|
+
keys: {
|
|
33
|
+
pubkey: any;
|
|
34
|
+
isSigner: any;
|
|
35
|
+
isWritable: any;
|
|
36
|
+
}[];
|
|
37
|
+
data: Uint8Array<any>;
|
|
38
|
+
};
|
|
39
|
+
/** The update authority a Token Metadata record names (who can change its name, ticker and link). */
|
|
40
|
+
export declare const metadataUpdateAuthority: (data: any) => string | null;
|
package/types/client/sizes.d.ts
CHANGED
|
@@ -41,6 +41,19 @@ export declare function entryRows(entries: any, items?: {}): any;
|
|
|
41
41
|
* gives items. A pack's order grows with its cards.
|
|
42
42
|
*/
|
|
43
43
|
export declare function orderBytes(lootBox: any, shares: number | undefined, opens: number | undefined, items: any): number;
|
|
44
|
+
/** The most draws one `open_box` takes in all, its count times the box's draws: what one answer could weigh, each draw
|
|
45
|
+
* at its weighing at least (`ROLL_BUDGET / COST_WEIGH`, 300). More is refused before any price is added up
|
|
46
|
+
* (`PackTooLarge`). */
|
|
47
|
+
export declare const MOST_DRAWS_A_PURCHASE: number;
|
|
48
|
+
/** The most sales one `buy` takes (`count`): what one transaction can price (`MAX_TX_CU / COST_SALE`, rounded down:
|
|
49
|
+
* 25,454). More is refused before any price is added up (`PackTooLarge`). */
|
|
50
|
+
export declare const MOST_SALES_A_BUY: number;
|
|
51
|
+
/**
|
|
52
|
+
* The tries counted for finding a pool's or a stash's address when the project is not known yet (a spec checked before
|
|
53
|
+
* any wallet is named): 2, what an address takes on average (half are found at the first try, a quarter at the second,
|
|
54
|
+
* and so on). With the project known, the count is exact: `256 - bump` tries an address.
|
|
55
|
+
*/
|
|
56
|
+
export declare const ASSUMED_TRIES = 2;
|
|
44
57
|
/**
|
|
45
58
|
* The heap reading a box takes beyond its entries (`order::box_bytes`): its odds, gate, shares, subject, slots and
|
|
46
59
|
* fates, read whole with it, by their bytes and their number. `box`: the box's args, as `upsertBoxIx` takes them.
|
|
@@ -53,20 +66,30 @@ export declare function boxBytes({ odds, gate, shares, subject, slots, fates }:
|
|
|
53
66
|
slots?: never[] | undefined;
|
|
54
67
|
subject?: null | undefined;
|
|
55
68
|
}): bigint;
|
|
69
|
+
/**
|
|
70
|
+
* The tries finding a pool's (`pool` true) or a stash's address takes (`order::searched`: `256 - bump`), for `project`'s
|
|
71
|
+
* pool or stash `id`. With no project, `ASSUMED_TRIES`.
|
|
72
|
+
*/
|
|
73
|
+
export declare function searchTries(project: any, pool: any, id: any): number;
|
|
56
74
|
/**
|
|
57
75
|
* Whether all of `opens` opens of `entries` by `slots` fit one oracle answer (`order::draws_fit`), each draw at the
|
|
58
|
-
* dearest its slot could make, with what is known from the box alone (no table rows, a stash of one group
|
|
59
|
-
* `shares` and `fates` counted, and `bytes` (`boxBytes`) of heap reading the box
|
|
60
|
-
* be a number: that many draws among every entry. The answer's heap counts for a
|
|
61
|
-
* the oracle (`oracle`: a box that doesn't roll but one `buy` can't carry,
|
|
62
|
-
*
|
|
63
|
-
*
|
|
76
|
+
* dearest its slot could make, with what is known from the box alone (no table rows, a stash of one group, a named
|
|
77
|
+
* NFT's stash of one row), its `odds`, `shares` and `fates` counted, and `bytes` (`boxBytes`) of heap reading the box
|
|
78
|
+
* takes beyond its entries. `slots` may be a number: that many draws among every entry. The answer's heap counts for a
|
|
79
|
+
* box that rolls, or one sold through the oracle (`oracle`: a box that doesn't roll but one `buy` can't carry,
|
|
80
|
+
* `buyFits`). `project`: the project the box is in, whose pools' and stashes' addresses the write searches for (each
|
|
81
|
+
* search counted at `256 - bump` tries of 1,500 units); without it, at `ASSUMED_TRIES` tries an address. The program
|
|
82
|
+
* refuses a box whose one open doesn't fit at its upsert (`PackTooLarge`). `purchase`: counted as `open_box` counts a
|
|
83
|
+
* purchase of `opens` instead (the answer's heap always; a draw's own stock read again only when there are several; at
|
|
84
|
+
* most `MOST_DRAWS_A_PURCHASE` draws in all), from what the box alone tells: the program reads the accounts too.
|
|
64
85
|
*/
|
|
65
|
-
export declare function drawsFit(entries: any, slots: any, opens?: number, { odds, shares, fates, bytes, oracle }?: {
|
|
86
|
+
export declare function drawsFit(entries: any, slots: any, opens?: number, { odds, shares, fates, bytes, oracle, project, purchase }?: {
|
|
66
87
|
bytes?: bigint | undefined;
|
|
67
88
|
fates?: number | undefined;
|
|
68
89
|
odds?: number | undefined;
|
|
69
90
|
oracle?: boolean | undefined;
|
|
91
|
+
project?: null | undefined;
|
|
92
|
+
purchase?: boolean | undefined;
|
|
70
93
|
shares?: number | undefined;
|
|
71
94
|
}): boolean;
|
|
72
95
|
/**
|
|
@@ -76,10 +99,21 @@ export declare function drawsFit(entries: any, slots: any, opens?: number, { odd
|
|
|
76
99
|
* (`drawsFit` with `oracle`), and can't give a roll (`PackTooLarge`). `box`: the box's args.
|
|
77
100
|
*/
|
|
78
101
|
export declare function buyFits(box: any, draws?: any): boolean;
|
|
102
|
+
/**
|
|
103
|
+
* Whether the program writes `box` (its args, as `upsertBoxIx` takes them, or a box read from the chain) as far as what
|
|
104
|
+
* it costs goes (`catalog::loot_box`, `PackTooLarge`): a box that doesn't roll sells by `buy` when one transaction carries
|
|
105
|
+
* the whole purchase (`buyFits`), else through the oracle, whose heap then counts and which can't give a roll; and every
|
|
106
|
+
* draw of one open fits one answer (`drawsFit`). `project`: whose pools and stashes the box names (a box read from the
|
|
107
|
+
* chain says); without it, each search at `ASSUMED_TRIES` tries.
|
|
108
|
+
*/
|
|
109
|
+
export declare function boxFits(box: any, { project }?: {
|
|
110
|
+
project?: any;
|
|
111
|
+
}): boolean;
|
|
79
112
|
/** The most program rent and fee one order takes from its player, in lamports (0.01 SOL): a client cap (`opensAnOrder`). */
|
|
80
113
|
export declare const ORDER_COST_CAP = 10000000n;
|
|
81
114
|
/**
|
|
82
|
-
* The most opens of `lootBox` a client puts in one order: what one oracle answer draws (`drawsFit`
|
|
115
|
+
* The most opens of `lootBox` a client puts in one order: what one oracle answer draws (`drawsFit` as a purchase counts
|
|
116
|
+
* it, the box's project's searches counted when the box names it: a box read from the chain does), and what keeps the
|
|
83
117
|
* order's rent and the player's fee within `ORDER_COST_CAP` (`orderBytes` with `items`, so with the box's shares). At
|
|
84
118
|
* least one: a box whose one open is over the cap is still bought one open an order. A purchase of more is several
|
|
85
119
|
* orders, each its own answer.
|
|
@@ -87,12 +87,16 @@ export declare const FAR_SECONDS: number;
|
|
|
87
87
|
/** A Unix time in words, in English: "14 March 2031". Past what a date can show, the year alone. */
|
|
88
88
|
export declare function dateWords(seconds: any): string;
|
|
89
89
|
/**
|
|
90
|
-
* Checks an economy spec. `now` (Unix seconds) dates "publish".
|
|
90
|
+
* Checks an economy spec. `now` (Unix seconds) dates "publish". `project`: the project's address, once a wallet is named
|
|
91
|
+
* (the plan for a real setup passes it; a live economy's file names it): a box's write searches for each of its pools'
|
|
92
|
+
* and stashes' addresses, and the checker counts each search exactly with it, else at an assumed 2 tries an address
|
|
93
|
+
* (`ASSUMED_TRIES`).
|
|
91
94
|
* Returns { ok, refusals: [{ code, where, message, fix }], warnings: [{ code, where, message, fix? }] }.
|
|
92
95
|
*/
|
|
93
|
-
export declare function check(economy: any, { network, now }?: {
|
|
96
|
+
export declare function check(economy: any, { network, now, project }?: {
|
|
94
97
|
network?: string | undefined;
|
|
95
98
|
now?: number | undefined;
|
|
99
|
+
project?: any;
|
|
96
100
|
}): {
|
|
97
101
|
ok: boolean;
|
|
98
102
|
refusals: any[];
|
package/types/economy/cost.d.mts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
export declare function setupCost(plan: any, economy: any): {
|
|
3
3
|
accounts: any;
|
|
4
4
|
rentSol: any;
|
|
5
|
+
metadataFeesSol: any;
|
|
5
6
|
programFeesSol: any;
|
|
6
7
|
networkFeesSol: any;
|
|
7
8
|
seeds: {};
|
|
@@ -40,6 +41,7 @@ export declare function cost(economy: any, { now, only, live }?: {
|
|
|
40
41
|
steps: any[];
|
|
41
42
|
built: any[];
|
|
42
43
|
instructions: any;
|
|
44
|
+
compute: boolean;
|
|
43
45
|
signers: any[];
|
|
44
46
|
alone: boolean;
|
|
45
47
|
}[];
|
|
@@ -50,6 +52,7 @@ export declare function cost(economy: any, { now, only, live }?: {
|
|
|
50
52
|
setup: {
|
|
51
53
|
accounts: any;
|
|
52
54
|
rentSol: any;
|
|
55
|
+
metadataFeesSol: any;
|
|
53
56
|
programFeesSol: any;
|
|
54
57
|
networkFeesSol: any;
|
|
55
58
|
seeds: {};
|
package/types/economy/live.d.mts
CHANGED
|
@@ -33,6 +33,7 @@ export declare function prepare(economy: any, { owner, keys, live, now, rent, on
|
|
|
33
33
|
steps: any[];
|
|
34
34
|
built: any[];
|
|
35
35
|
instructions: any;
|
|
36
|
+
compute: boolean;
|
|
36
37
|
signers: any[];
|
|
37
38
|
alone: boolean;
|
|
38
39
|
}[];
|
|
@@ -68,6 +69,7 @@ export declare function prepare(economy: any, { owner, keys, live, now, rent, on
|
|
|
68
69
|
steps: any[];
|
|
69
70
|
built: any[];
|
|
70
71
|
instructions: any;
|
|
72
|
+
compute: boolean;
|
|
71
73
|
signers: any[];
|
|
72
74
|
alone: boolean;
|
|
73
75
|
}[];
|