@xgamesfun/solana 0.28.0 → 0.28.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/DISCLAIMER.md +3 -1
- package/README.md +18 -16
- package/bin/xsolana.mjs +27 -1
- package/js/approve/details.mjs +3 -3
- package/js/approve/live-check.mjs +2 -2
- package/js/approve/page.html +5 -5
- package/js/approve/page.js +24 -4
- package/js/approve/server.mjs +66 -78
- package/js/approve/wallet-adds.mjs +180 -0
- package/js/approve/with-key.mjs +7 -4
- package/js/chain/borsh.js +5 -3
- package/js/chain/pda.js +6 -0
- package/js/chain/rpc.js +26 -10
- package/js/client/accounts.js +6 -6
- package/js/client/cluster.js +136 -0
- package/js/client/grant.js +2 -2
- package/js/client/ix.js +16 -16
- package/js/client/pda.js +18 -5
- package/js/economy/check.mjs +28 -13
- package/js/economy/explain.mjs +2 -1
- package/js/economy/guide.mjs +6 -4
- package/js/economy/live.mjs +2 -2
- package/js/economy/plan.mjs +3 -3
- package/js/economy/schema.json +2 -2
- package/js/mcp/server.mjs +35 -4
- package/js/mcp/tools.mjs +56 -45
- package/js/mcp/with-cluster.mjs +25 -0
- package/js/play/index.js +42 -37
- package/js/play/sponsor.js +4 -4
- package/js/results/index.js +7 -6
- package/js/results/server.js +4 -3
- package/js/results/wire.js +9 -8
- package/package.json +6 -1
- package/release.json +6 -1
- package/skills/xsolana-economy/SKILL.md +13 -9
- package/skills/xsolana-economy/references/apps.md +2 -1
- package/skills/xsolana-economy/references/telling-the-person.md +1 -1
- package/skills/xsolana-economy/references/trust-and-cheating.md +1 -1
- package/types/chain/borsh.d.ts +4 -2
- package/types/chain/pda.d.ts +6 -0
- package/types/chain/rpc.d.ts +10 -4
- package/types/client/cluster.d.ts +61 -0
- package/types/client/idl/loot.d.ts +9 -9
- package/types/client/pda.d.ts +1 -1
- package/types/economy/check.d.mts +1 -2
- package/types/economy/plan.d.mts +16 -16
- package/types/mcp/server.d.mts +1 -1
- package/types/mcp/tools.d.mts +9 -7
- package/types/mcp/with-cluster.d.mts +8 -0
- package/types/play/index.d.ts +2 -2
- package/types/results/index.d.ts +6 -6
- package/types/results/server.d.ts +3 -3
- package/types/results/wire.d.ts +8 -8
package/js/client/pda.js
CHANGED
|
@@ -3,9 +3,22 @@
|
|
|
3
3
|
import { addressBytes } from '../chain/base58.js';
|
|
4
4
|
import { intLE, utf8, uintLE } from '../chain/bytes.js';
|
|
5
5
|
import { associatedTokenAddress, findProgramAddress, MPL_CORE_PROGRAM, TOKEN_PROGRAM } from '../chain/pda.js';
|
|
6
|
-
import
|
|
6
|
+
import { lootProgram } from './cluster.js';
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
// loot's program on this process's cluster (`cluster.js`): devnet unless the process names mainnet.
|
|
9
|
+
export {
|
|
10
|
+
checkOnCluster,
|
|
11
|
+
CLUSTERS,
|
|
12
|
+
cluster,
|
|
13
|
+
clusterRpcUrl,
|
|
14
|
+
LOOT_PROGRAM,
|
|
15
|
+
lootProgram,
|
|
16
|
+
networkOf,
|
|
17
|
+
realMoney,
|
|
18
|
+
scopeClusters,
|
|
19
|
+
useCluster,
|
|
20
|
+
walletChain,
|
|
21
|
+
} from './cluster.js';
|
|
9
22
|
/** MagicBlock's VRF program and its default queue, the only queue the program accepts. */
|
|
10
23
|
export const VRF_PROGRAM = 'Vrf1RNUjXmQGjmQrQLvJHs9SNkvDJEsRVFPkfSQUwGz';
|
|
11
24
|
export const VRF_QUEUE = 'Cuj97ggrhhidhbu39TijNVqE74xvKJ69gDervRUXAxGh';
|
|
@@ -25,7 +38,7 @@ const u64 = n => uintLE(n, 8);
|
|
|
25
38
|
const i64 = n => intLE(n, 8);
|
|
26
39
|
|
|
27
40
|
/** The PDA of these seeds under the loot program, with its bump. */
|
|
28
|
-
export const lootAddress = (seeds, program =
|
|
41
|
+
export const lootAddress = (seeds, program = lootProgram()) => findProgramAddress(seeds, program);
|
|
29
42
|
const at = (...seeds) => lootAddress(seeds)[0];
|
|
30
43
|
|
|
31
44
|
export const pda = {
|
|
@@ -104,9 +117,9 @@ export const pda = {
|
|
|
104
117
|
/** The program's identity, which signs its requests to the oracle. */
|
|
105
118
|
identity: () => at(seed('identity')),
|
|
106
119
|
/** The identity the oracle signs callbacks with: the VRF program's PDA for this program. */
|
|
107
|
-
oracleIdentity: () => findProgramAddress([seed('identity'), key(
|
|
120
|
+
oracleIdentity: () => findProgramAddress([seed('identity'), key(lootProgram())], VRF_PROGRAM)[0],
|
|
108
121
|
/** The program's ProgramData account (its upgrade authority signs `init_platform`). */
|
|
109
|
-
programData: () => findProgramAddress([key(
|
|
122
|
+
programData: () => findProgramAddress([key(lootProgram())], BPF_LOADER_UPGRADEABLE)[0],
|
|
110
123
|
};
|
|
111
124
|
|
|
112
125
|
/** `owner`'s associated token account of `mint` (owner may be a PDA: a project's vault, a pool is not one). */
|
package/js/economy/check.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import IDL from '../client/idl/loot.js';
|
|
|
8
8
|
import { gateList } from '../client/grant.js';
|
|
9
9
|
import { MAX_BID_NFTS, MAX_SHARES } from '../client/ix.js';
|
|
10
10
|
import { MAX_POTS } from '../client/accounts.js';
|
|
11
|
-
import { vault } from '../client/pda.js';
|
|
11
|
+
import { cluster, CLUSTERS, networkOf, realMoney, useCluster, vault } from '../client/pda.js';
|
|
12
12
|
import { ASSUMED_TRIES, boxBytes, buyFits, drawsFit, MAX_ORDER_BYTES, tableBudget } from '../client/sizes.js';
|
|
13
13
|
import { TOKEN_2022_PROGRAM, TOKEN_PROGRAM } from '../chain/pda.js';
|
|
14
14
|
import { addressBytes, encodeBase58 } from '../chain/base58.js';
|
|
@@ -693,10 +693,7 @@ export function dateWords(seconds) {
|
|
|
693
693
|
* (`ASSUMED_TRIES`).
|
|
694
694
|
* Returns { ok, refusals: [{ code, where, message, fix }], warnings: [{ code, where, message, fix? }] }.
|
|
695
695
|
*/
|
|
696
|
-
export function check(
|
|
697
|
-
economy,
|
|
698
|
-
{ network = 'devnet', now = Math.floor(Date.now() / 1000), project = economy?.live?.project ?? null } = {},
|
|
699
|
-
) {
|
|
696
|
+
export function check(economy, { now = Math.floor(Date.now() / 1000), project = economy?.live?.project ?? null } = {}) {
|
|
700
697
|
const refusals = [];
|
|
701
698
|
const warnings = [];
|
|
702
699
|
const refuse = (code, where, message, fix) => refusals.push({ code, where, message, fix });
|
|
@@ -717,13 +714,27 @@ export function check(
|
|
|
717
714
|
`The economy names ${STAND_IN_OWNER}, the address the setup plan stands in for your wallet before one connects. Nobody holds its key: whatever is sent there is lost.`,
|
|
718
715
|
'Name a real wallet there.',
|
|
719
716
|
);
|
|
720
|
-
|
|
717
|
+
// The economy's cluster: the network it names, devnet when it names none. This process works on one cluster, and
|
|
718
|
+
// the first economy it checks names it when nothing did before.
|
|
719
|
+
const named = networkOf(economy);
|
|
720
|
+
if (!Object.hasOwn(CLUSTERS, named))
|
|
721
721
|
refuse(
|
|
722
722
|
'E-NETWORK',
|
|
723
723
|
'economy.network',
|
|
724
|
-
`The economy names the network “${
|
|
725
|
-
'Set the network to "devnet".',
|
|
724
|
+
`The economy names the network “${named}”. loot runs on ${Object.keys(CLUSTERS).join(' and on ')}.`,
|
|
725
|
+
'Set the network to "devnet", or to "mainnet".',
|
|
726
726
|
);
|
|
727
|
+
else
|
|
728
|
+
try {
|
|
729
|
+
useCluster(named);
|
|
730
|
+
} catch {
|
|
731
|
+
refuse(
|
|
732
|
+
'E-NETWORK',
|
|
733
|
+
'economy.network',
|
|
734
|
+
`The economy is on ${named}, and this process works on ${cluster()}: one process works on one cluster.`,
|
|
735
|
+
`Run it where the cluster is ${named}: the xsolana command takes the cluster from the file, and a server or a script is started with XSOLANA_CLUSTER=${named}.`,
|
|
736
|
+
);
|
|
737
|
+
}
|
|
727
738
|
const oldVersion = economy?.version === 1;
|
|
728
739
|
if (oldVersion)
|
|
729
740
|
refuse(
|
|
@@ -4925,7 +4936,9 @@ export function check(
|
|
|
4925
4936
|
'W-REVENUE-SHARE',
|
|
4926
4937
|
where,
|
|
4927
4938
|
`${label(f)} pays stakers a share of what players spend (${revenue.map(x => x.by).join(', ')}). Paying holders out of a project’s revenue is what regulators look at when they decide whether a token is a security.`,
|
|
4928
|
-
|
|
4939
|
+
realMoney()
|
|
4940
|
+
? 'This economy is on mainnet, where that is real money: ask counsel first, or reward stakers from emissions (a schedule that mints a game token) rather than from sales.'
|
|
4941
|
+
: 'Fine on devnet. Before mainnet, ask counsel, or reward stakers from emissions (a schedule that mints a game token) rather than from sales.',
|
|
4929
4942
|
);
|
|
4930
4943
|
}
|
|
4931
4944
|
|
|
@@ -5030,7 +5043,9 @@ export function check(
|
|
|
5030
5043
|
'W-TOKEN-SALE',
|
|
5031
5044
|
`${where}.prize`,
|
|
5032
5045
|
`${label(c)} sells ${sold.symbol ?? sold.key} for ${tok.symbol ?? tok.key}: each backer pays in and takes a share of the tokens by what they put in. Selling a token to raise money is what securities law looks at first, and many places limit who may buy.`,
|
|
5033
|
-
|
|
5046
|
+
realMoney()
|
|
5047
|
+
? 'This economy is on mainnet, where that is real money: ask counsel first.'
|
|
5048
|
+
: 'Fine on devnet. Before mainnet, ask counsel.',
|
|
5034
5049
|
);
|
|
5035
5050
|
// One NFT, the last units of an item, a pool's prize: the winning entries share it in the order they are claimed.
|
|
5036
5051
|
const scarce =
|
|
@@ -5119,7 +5134,7 @@ export function check(
|
|
|
5119
5134
|
warn(
|
|
5120
5135
|
'W-CHANCE-PRIZE',
|
|
5121
5136
|
where,
|
|
5122
|
-
`${label(c)} lets players put money on an outcome: that is betting, which is regulated in most places once it is real money (state rules on prediction markets are in flux).
|
|
5137
|
+
`${label(c)} lets players put money on an outcome: that is betting, which is regulated in most places once it is real money (state rules on prediction markets are in flux). ${realMoney() ? 'This economy is on mainnet: it is real money.' : 'This economy is on devnet, which is not real money.'}`,
|
|
5123
5138
|
'Before mainnet: counsel, an age and region gate, and a name that does not call it an investment.',
|
|
5124
5139
|
);
|
|
5125
5140
|
}
|
|
@@ -5688,14 +5703,14 @@ export function check(
|
|
|
5688
5703
|
warn(
|
|
5689
5704
|
'W-CHANCE-ITEMS',
|
|
5690
5705
|
'economy',
|
|
5691
|
-
|
|
5706
|
+
`Players pay for a random collectible they can resell (a loot box or gacha). Some places treat paid loot boxes of tradeable items as gambling, and many require the odds to be shown. ${realMoney() ? 'This economy is on mainnet, so it is real money: counsel and an age and region gate come first.' : 'This economy is on devnet; before mainnet, counsel and an age and region gate.'}`,
|
|
5692
5707
|
'Tell the person in plain words: paid random items that can be resold may count as gambling in some places; show the odds on the page, and get a lawyer before real money.',
|
|
5693
5708
|
);
|
|
5694
5709
|
if (chance)
|
|
5695
5710
|
warn(
|
|
5696
5711
|
'W-CHANCE-PRIZE',
|
|
5697
5712
|
'economy',
|
|
5698
|
-
|
|
5713
|
+
`Players pay for a chance at money (a box that pays a prize, a lottery). That is gambling in many places once it is real money, and paid packs with a cash buyback are drawing lawsuits. ${realMoney() ? 'This economy is on mainnet, so it is real money: counsel and an age and region gate come first.' : 'This economy is on devnet; before mainnet, counsel and an age and region gate.'}`,
|
|
5699
5714
|
'Tell the person this in plain words before they go live: paying for a chance at money is gambling in many places, so before real money they need a lawyer and an age and region gate.',
|
|
5700
5715
|
);
|
|
5701
5716
|
|
package/js/economy/explain.mjs
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
import { farmEnds, playSteps } from './plan.mjs';
|
|
29
29
|
import { TRUST_WORDS } from './trust.mjs';
|
|
30
30
|
import { inAll, poolFate, round, simulate, unnamedExceptions, unnamedWords } from './simulate.mjs';
|
|
31
|
+
import { realMoney } from '../client/cluster.js';
|
|
31
32
|
|
|
32
33
|
const plural = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
|
|
33
34
|
const num = x => (Number.isFinite(x) ? String(round(x, 6)) : String(x));
|
|
@@ -613,7 +614,7 @@ export function explainEconomy(economy, { now = Math.floor(Date.now() / 1000) }
|
|
|
613
614
|
);
|
|
614
615
|
lines.push(
|
|
615
616
|
'',
|
|
616
|
-
'
|
|
617
|
+
`${realMoney() ? 'This economy is on mainnet: what it takes and what it pays is real money, and loot has not been independently audited.' : 'This economy is on devnet: nothing here is real money.'} Going live is signed on your own machine by the project’s key file, which is what “your wallet” means above. The command first prints everything it would send and sends nothing; it sends only when run again with the code it printed, which your agent is told to do only after your yes. No seed phrase or wallet key is ever asked for.`,
|
|
617
618
|
);
|
|
618
619
|
return {
|
|
619
620
|
ok: true,
|
package/js/economy/guide.mjs
CHANGED
|
@@ -123,7 +123,7 @@ export const CONVENTIONS = [
|
|
|
123
123
|
'A box, a shop item and a recipe take `opens` and `closes` (times), and `perWallet` (in all) and `perWalletEach` ({ "count": 1, "every": "1d" }) to limit each wallet.',
|
|
124
124
|
'Leave out `id`, `index`, `shopBox` and `live`: they are written when the economy goes live.',
|
|
125
125
|
'Public data only. A spec never holds a private key or a seed phrase (the checker refuses one, E-SECRET).',
|
|
126
|
-
'
|
|
126
|
+
'"network" is "devnet" (what a file that names none is on: nothing there is real money) or "mainnet" (real money). loot has an address of its own on each, and an economy is on one: build and try it on devnet first.',
|
|
127
127
|
];
|
|
128
128
|
|
|
129
129
|
/**
|
|
@@ -252,8 +252,10 @@ export const CANNOT = [
|
|
|
252
252
|
{
|
|
253
253
|
what: 'mainnet',
|
|
254
254
|
words: 'mainnet real money production',
|
|
255
|
-
why: '
|
|
256
|
-
nearest: [
|
|
255
|
+
why: 'loot runs on mainnet, with real money, and has not been independently audited; an economy is on devnet unless its file says "network": "mainnet"',
|
|
256
|
+
nearest: [
|
|
257
|
+
'try it on devnet first, then set "network": "mainnet" in a copy of the file and go live again with real SOL',
|
|
258
|
+
],
|
|
257
259
|
},
|
|
258
260
|
];
|
|
259
261
|
|
|
@@ -431,7 +433,7 @@ export function guideText(only) {
|
|
|
431
433
|
return [
|
|
432
434
|
only
|
|
433
435
|
? `The part “${only}” of a loot economy (* = required):`
|
|
434
|
-
: `A loot economy is one JSON spec (xsolana.json) on the shared loot program ${IDL.metadata.version}, devnet
|
|
436
|
+
: `A loot economy is one JSON spec (xsolana.json) on the shared loot program ${IDL.metadata.version}, on devnet or on mainnet (its "network"; devnet when it names none). Nobody writes a contract: the spec is checked, simulated, and put live on the owner’s own machine, signed by the project’s key file once they have said yes. Its parts (* = required field):`,
|
|
435
437
|
...parts.flatMap(p => [
|
|
436
438
|
`\n${p.part} (${p.type}): ${p.says}${p.atPlayTime ? `\n At play time: ${p.atPlayTime}.` : ''}`,
|
|
437
439
|
...p.fields.map(fieldLine),
|
package/js/economy/live.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// the approval page (js/approve) has the owner's wallet sign, and the full simulation sends them to a local chain.
|
|
3
3
|
import { encodeBase58 } from '../chain/base58.js';
|
|
4
4
|
import { randomBytes } from '../chain/bytes.js';
|
|
5
|
-
import {
|
|
5
|
+
import { lootProgram } from '../client/pda.js';
|
|
6
6
|
import { compressedTree, listOf, model, tokenLabels } from './model.mjs';
|
|
7
7
|
import { auctionSeller, plan, setupContext } from './plan.mjs';
|
|
8
8
|
|
|
@@ -95,7 +95,7 @@ export function recordLanded(economy, { ctx, signatures = [], at = new Date().to
|
|
|
95
95
|
};
|
|
96
96
|
const { live: _, previous: __, ...published } = out;
|
|
97
97
|
out.live = {
|
|
98
|
-
program:
|
|
98
|
+
program: lootProgram(),
|
|
99
99
|
project: ctx.project,
|
|
100
100
|
// A project made with no collection (`"collection": false`) has none to write: the schema takes an address only.
|
|
101
101
|
...(ctx.collection ? { collection: ctx.collection } : {}),
|
package/js/economy/plan.mjs
CHANGED
|
@@ -1649,7 +1649,7 @@ const TABLE_GROUP = 24;
|
|
|
1649
1649
|
*/
|
|
1650
1650
|
export function standingAddresses(m) {
|
|
1651
1651
|
const fixed = [
|
|
1652
|
-
loot.
|
|
1652
|
+
loot.lootProgram(),
|
|
1653
1653
|
loot.pda.platform(),
|
|
1654
1654
|
loot.pda.identity(),
|
|
1655
1655
|
loot.VRF_PROGRAM,
|
|
@@ -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
|
|
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/js/economy/schema.json
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
"description": "The spec format: 2 is loot 0.7 and later."
|
|
13
13
|
},
|
|
14
14
|
"network": {
|
|
15
|
-
"
|
|
16
|
-
"description": "
|
|
15
|
+
"enum": ["devnet", "mainnet"],
|
|
16
|
+
"description": "The cluster the economy is on: devnet when it names none. loot has an address of its own on each."
|
|
17
17
|
},
|
|
18
18
|
"name": {
|
|
19
19
|
"type": "string",
|
package/js/mcp/server.mjs
CHANGED
|
@@ -5,13 +5,20 @@
|
|
|
5
5
|
// (`xsolana tool`), with the same words. Answers go out as text and as structuredContent.
|
|
6
6
|
//
|
|
7
7
|
// stdout is the protocol: everything else goes to stderr.
|
|
8
|
+
import { readFile } from 'node:fs/promises';
|
|
9
|
+
import path from 'node:path';
|
|
8
10
|
import { validate } from '../economy/validate.mjs';
|
|
9
|
-
import {
|
|
11
|
+
import { CLUSTERS, cluster, networkOf } from '../client/cluster.js';
|
|
12
|
+
import { PACKAGE_VERSION, REMOTE_TOOLS, tools, VERSION } from './tools.mjs';
|
|
13
|
+
import { withCluster } from './with-cluster.mjs';
|
|
14
|
+
|
|
15
|
+
/** Where loot runs, for every agent that connects. */
|
|
16
|
+
const WHERE = `loot ${VERSION} runs on devnet (program ${CLUSTERS.devnet.program}) and on mainnet (program ${CLUSTERS.mainnet.program}). An economy is on devnet, where nothing is real money, unless its file says "network": "mainnet", where everything is. Build and try an economy on devnet first. loot has not been independently audited.`;
|
|
10
17
|
|
|
11
18
|
export const INSTRUCTIONS = [
|
|
12
19
|
'loot is one shared Solana program for game and community economies (shops, loot boxes, lotteries, auctions, staking farms, crafting, contests, schedules, perks for holders of any NFT, capped rewards). An economy is a JSON spec, not a new smart contract: never write Anchor or Rust for what loot covers.',
|
|
13
20
|
'Start with xsolana_guide, then xsolana_examples with the person’s own words; write the spec; xsolana_check after every change; xsolana_simulate and xsolana_explain before telling them what it does; xsolana_review before going live (fix its high findings, say honestly what each voucher server checks, and tell the person what a cheater gets); xsolana_save; xsolana_publish when they say to put it live. Going live is signed by a key file on their own machine (xsolana keygen makes one; never ask for, read or print a key) with xsolana approve --keypair, which prints everything it would send and sends nothing until it is run again with the code it printed: do that only after they have read the printout and said yes. That key owns the project for good: a change to an economy that is already live is signed by the key that owns it, never by a new one. A browser wallet’s signing page (approve --browser) only if they ask for one, and they run it themselves.',
|
|
14
|
-
|
|
21
|
+
WHERE,
|
|
15
22
|
].join(' ');
|
|
16
23
|
|
|
17
24
|
/**
|
|
@@ -22,7 +29,7 @@ export const REMOTE_INSTRUCTIONS = [
|
|
|
22
29
|
INSTRUCTIONS.split(' Start with')[0],
|
|
23
30
|
'Start with xsolana_guide, then xsolana_examples with the person’s own words; write the spec and pass it whole as "spec" to every tool (this hosted server reads no files); xsolana_check after every change; xsolana_simulate and xsolana_explain before telling them what it does; xsolana_review before going live (fix its high findings, say honestly what each voucher server checks, and tell the person what a cheater gets).',
|
|
24
31
|
`To go live, the spec goes in a file named xsolana.json on the person’s own computer and is signed there by a key file. In that folder: npx -y @xgamesfun/solana@${PACKAGE_VERSION} keygen ~/.config/xsolana/<name>.json makes the key file (it prints the address, never the key; the person sends that address devnet SOL), then npx -y @xgamesfun/solana@${PACKAGE_VERSION} approve xsolana.json --keypair <that file> prints everything that would be sent and sends nothing. When the person has read that and said yes, the same command with --yes <the code it printed> sends it. Run them yourself if you have a terminal there; otherwise hand them to the person. Never ask for, read or print a key. Say once what the key file is: the project’s owner for good (whoever can read it controls the project; they back it up themselves; nothing restores a lost one) and, unless the spec names a treasury, where what the economy earns lands. A change to an economy that is already live is signed by the key that owns it: never make a new key for it. A browser wallet’s signing page (the same approve command with --browser in place of --keypair), which the person runs themselves, only if they ask for one or a browser wallet owns the project. (xsolana_save and xsolana_publish, which tool texts may name, run only in the local server.)`,
|
|
25
|
-
|
|
32
|
+
WHERE,
|
|
26
33
|
].join(' ');
|
|
27
34
|
|
|
28
35
|
/** A tool as tools/list shows it; on a hosted server, without the `file` argument it can't take. */
|
|
@@ -67,12 +74,36 @@ export async function runTool(name, input = {}, ctx = {}) {
|
|
|
67
74
|
const bad = inputProblems(tool.inputSchema, input);
|
|
68
75
|
if (bad.length) return { isError: true, text: bad.join('; ') };
|
|
69
76
|
try {
|
|
70
|
-
|
|
77
|
+
// Each tool runs on the cluster of the economy it is given, so one server answers for devnet and mainnet alike.
|
|
78
|
+
return await withCluster(await clusterOfInput(input, ctx), () => tool.handler(input, ctx));
|
|
71
79
|
} catch (e) {
|
|
72
80
|
return { isError: true, text: e.message };
|
|
73
81
|
}
|
|
74
82
|
}
|
|
75
83
|
|
|
84
|
+
/**
|
|
85
|
+
* The cluster a tool's call is for: the "network" of the economy it is given (passed whole as `spec`, or read from its
|
|
86
|
+
* `file`, which is xsolana.json when none is named), devnet when the economy names none. With no economy to read (no
|
|
87
|
+
* file there, a file that is not JSON, a tool that takes none) or a network loot does not know, it is the server's
|
|
88
|
+
* own cluster, and the tool says what is wrong in its own words.
|
|
89
|
+
*/
|
|
90
|
+
async function clusterOfInput(input, ctx) {
|
|
91
|
+
let economy = input?.spec && typeof input.spec === 'object' ? input.spec : null;
|
|
92
|
+
if (!economy && !ctx.remote) {
|
|
93
|
+
try {
|
|
94
|
+
const file = path.resolve(
|
|
95
|
+
ctx.cwd ?? process.cwd(),
|
|
96
|
+
typeof input?.file === 'string' ? input.file : 'xsolana.json',
|
|
97
|
+
);
|
|
98
|
+
economy = JSON.parse(await readFile(file, 'utf8'));
|
|
99
|
+
} catch {
|
|
100
|
+
economy = null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const named = economy && typeof economy === 'object' ? networkOf(economy) : null;
|
|
104
|
+
return named && Object.hasOwn(CLUSTERS, named) ? named : cluster();
|
|
105
|
+
}
|
|
106
|
+
|
|
76
107
|
const jsonable = value =>
|
|
77
108
|
JSON.parse(
|
|
78
109
|
JSON.stringify(value, (_, v) =>
|