@xgamesfun/solana 0.26.3 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -9
- package/js/approve/live-check.mjs +106 -1
- package/js/approve/page.html +1 -1
- package/js/approve/page.js +19 -5
- package/js/approve/server.mjs +192 -33
- package/js/chain/metadata.js +92 -0
- package/js/client/compressed.js +12 -5
- package/js/client/idl/loot.js +1 -1
- package/js/client/idl/loot.json +53 -7
- package/js/client/ix.js +44 -9
- package/js/economy/check.mjs +12 -4
- package/js/economy/cost.mjs +6 -5
- package/js/economy/explain.mjs +3 -2
- package/js/economy/guide.mjs +2 -2
- package/js/economy/live.mjs +27 -7
- package/js/economy/model.mjs +13 -2
- package/js/economy/plan.mjs +84 -27
- package/js/economy/schema.json +7 -1
- package/js/mcp/tools.mjs +31 -11
- package/js/play/index.js +519 -193
- package/package.json +1 -1
- package/release.json +5 -5
- package/skills/xsolana-economy/SKILL.md +4 -4
- package/skills/xsolana-economy/references/apps.md +6 -0
- package/skills/xsolana-economy/references/telling-the-person.md +4 -2
- package/skills/xsolana-economy/references/trust-and-cheating.md +4 -3
- package/types/chain/metadata.d.ts +45 -0
- package/types/client/compressed.d.ts +5 -2
- package/types/client/ix.d.ts +38 -7
- package/types/economy/cost.d.mts +11 -3
- package/types/economy/live.d.mts +29 -5
- package/types/economy/model.d.mts +6 -0
- package/types/economy/plan.d.mts +22 -5
- package/types/play/index.d.ts +214 -129
package/js/client/idl/loot.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"address": "9cwqM1TXytPnFZsLfmUyv2dg4uW6Ch9C6UJVPns7NFQ",
|
|
3
3
|
"metadata": {
|
|
4
4
|
"name": "loot",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.27.0",
|
|
6
6
|
"spec": "0.1.0",
|
|
7
7
|
"description": "Economy for Anything: one program every coin of a project passes through",
|
|
8
8
|
"repository": "https://github.com/gamesfun/loot"
|
|
@@ -8399,6 +8399,19 @@
|
|
|
8399
8399
|
128
|
|
8400
8400
|
]
|
|
8401
8401
|
},
|
|
8402
|
+
{
|
|
8403
|
+
"name": "NftGone",
|
|
8404
|
+
"discriminator": [
|
|
8405
|
+
254,
|
|
8406
|
+
200,
|
|
8407
|
+
63,
|
|
8408
|
+
241,
|
|
8409
|
+
105,
|
|
8410
|
+
40,
|
|
8411
|
+
174,
|
|
8412
|
+
76
|
|
8413
|
+
]
|
|
8414
|
+
},
|
|
8402
8415
|
{
|
|
8403
8416
|
"name": "OddsRaised",
|
|
8404
8417
|
"discriminator": [
|
|
@@ -11574,9 +11587,10 @@
|
|
|
11574
11587
|
"name": "reward_remainder",
|
|
11575
11588
|
"docs": [
|
|
11576
11589
|
"The carry: rounding of a share over the total weight, times `PRECISION`. The next move before",
|
|
11577
|
-
"the farm's end credits it to the seats there once it adds up to a share
|
|
11578
|
-
"
|
|
11579
|
-
"
|
|
11590
|
+
"the farm's end credits it to the seats there once it adds up to a share. Weight that leaves",
|
|
11591
|
+
"frees its share of the whole tokens (`Farm::free_share`); a move that leaves no weight, or any",
|
|
11592
|
+
"move once the farm is over, frees all of them (`FarmSide::settle`), and so does a walk that",
|
|
11593
|
+
"begins with no weight or where the weight reaches 0 (`Farm::walk`)."
|
|
11580
11594
|
],
|
|
11581
11595
|
"type": "u128"
|
|
11582
11596
|
},
|
|
@@ -13630,7 +13644,8 @@
|
|
|
13630
13644
|
"name": "LotteryPulled",
|
|
13631
13645
|
"docs": [
|
|
13632
13646
|
"One part of a round's entry was settled: `amount`, the tokens it settled (0 for an NFT), to the",
|
|
13633
|
-
"buyer, or for a recipient's part to the recipient."
|
|
13647
|
+
"buyer, or for a recipient's part to the recipient. An NFT ticket proven gone settles its part",
|
|
13648
|
+
"moving nothing, with `NftGone`."
|
|
13634
13649
|
],
|
|
13635
13650
|
"type": {
|
|
13636
13651
|
"kind": "struct",
|
|
@@ -13894,6 +13909,37 @@
|
|
|
13894
13909
|
]
|
|
13895
13910
|
}
|
|
13896
13911
|
},
|
|
13912
|
+
{
|
|
13913
|
+
"name": "NftGone",
|
|
13914
|
+
"docs": [
|
|
13915
|
+
"A record let go of its NFT because the NFT is proven gone from the project (`piece::still_held`):",
|
|
13916
|
+
"its collection's controller took it out of the project's hands or burned it. Nothing moved; the",
|
|
13917
|
+
"record closed (a hold, a listing), finished without it (an order), or lost its row (a stash), and",
|
|
13918
|
+
"its rent went where that record's close sends it. `record` is the hold, listing, order or stash;",
|
|
13919
|
+
"`by` who sent it, which anyone may."
|
|
13920
|
+
],
|
|
13921
|
+
"type": {
|
|
13922
|
+
"kind": "struct",
|
|
13923
|
+
"fields": [
|
|
13924
|
+
{
|
|
13925
|
+
"name": "project",
|
|
13926
|
+
"type": "pubkey"
|
|
13927
|
+
},
|
|
13928
|
+
{
|
|
13929
|
+
"name": "record",
|
|
13930
|
+
"type": "pubkey"
|
|
13931
|
+
},
|
|
13932
|
+
{
|
|
13933
|
+
"name": "asset",
|
|
13934
|
+
"type": "pubkey"
|
|
13935
|
+
},
|
|
13936
|
+
{
|
|
13937
|
+
"name": "by",
|
|
13938
|
+
"type": "pubkey"
|
|
13939
|
+
}
|
|
13940
|
+
]
|
|
13941
|
+
}
|
|
13942
|
+
},
|
|
13897
13943
|
{
|
|
13898
13944
|
"name": "NftPrice",
|
|
13899
13945
|
"docs": [
|
|
@@ -15176,8 +15222,8 @@
|
|
|
15176
15222
|
"docs": [
|
|
15177
15223
|
"A prize an order's settle did not give (`OrderSettled` lists it, an `OrderPartSettled` spans it): the one at",
|
|
15178
15224
|
"`at` of the order's `won`. Its player, settling their own order, gave it up (tokens or a",
|
|
15179
|
-
"collectible they can't receive, a stash's NFT put back),
|
|
15180
|
-
"hold it (its serial voided)."
|
|
15225
|
+
"collectible they can't receive, a stash's NFT put back), a compressed one's collection can't",
|
|
15226
|
+
"hold it (its serial voided), or a stash's NFT is proven gone from the project (with `NftGone`)."
|
|
15181
15227
|
],
|
|
15182
15228
|
"type": {
|
|
15183
15229
|
"kind": "struct",
|
package/js/client/ix.js
CHANGED
|
@@ -1504,6 +1504,10 @@ export const SETTLE_LAST = 4_294_967_295;
|
|
|
1504
1504
|
* them (one its collection won't let move). `giveUp`: the places in `won` of tokens or collectibles the player,
|
|
1505
1505
|
* signing, gives up because they can't receive them (an account its issuer froze, a collection that refuses the mint),
|
|
1506
1506
|
* so the rest of the order is not held up: tokens given up stay in the project's vault (`PrizeForgone` says which).
|
|
1507
|
+
* An NFT proven gone from the project is passed over the same way, with no choice of the player's (`piece`: the Core
|
|
1508
|
+
* asset itself, `tokenMetadata.evidence` in its kind, a compressed NFT's record proven again): the NFT the order rolled
|
|
1509
|
+
* on is not sent back, burned, given or stashed (`NftGone`), and a stash's NFT it won is not given (`PrizeForgone`
|
|
1510
|
+
* for it beside `NftGone`: read it as not given); everything else settles as rolled.
|
|
1507
1511
|
* `handBack`: the wallet the box's fate gives the NFT rolled on to
|
|
1508
1512
|
* (`receiverOf`) is an account of the loot program (`handsBack` of it as read), so the program gives it back to the
|
|
1509
1513
|
* player, and a Token Metadata NFT's `token` is the player's. `extra`: a hook's or a memo's accounts.
|
|
@@ -1650,6 +1654,8 @@ export const INCINERATOR = '1nc1nerator11111111111111111111111111111111';
|
|
|
1650
1654
|
* (`orderRefundable`). A purchase counted against a box's limit for each
|
|
1651
1655
|
* wallet is given back. A purchase that counts its draws in its box's `in_flight` (`order.reserved`, a box that stops
|
|
1652
1656
|
* selling when an entry runs out) names the box, whose count it leaves. `extra`: a hook's or a memo's accounts.
|
|
1657
|
+
* An NFT the order holds that is proven gone from the project (`subject`, as `piece` takes it) is not looked for: the
|
|
1658
|
+
* whole payment goes back all the same (`NftGone` beside `OrderRefunded`).
|
|
1653
1659
|
*/
|
|
1654
1660
|
export function refundIx({ signer, orderAddress, order, subject, programs, extra = [] }) {
|
|
1655
1661
|
const project = order.project;
|
|
@@ -1952,6 +1958,10 @@ export const madeAssets = (project, item, itemState, count = 1) =>
|
|
|
1952
1958
|
* NFT is any kind
|
|
1953
1959
|
* (`piece`): `asset` and `collection` for a Core asset, with `tokenMetadata` for a Token Metadata NFT, or `compressed`
|
|
1954
1960
|
* (after a `proveLeafIx`).
|
|
1961
|
+
* A hold whose NFT is proven gone from the project (`piece`: the Core asset itself, `tokenMetadata.evidence`, a
|
|
1962
|
+
* compressed NFT's record proven again) closes instead: anyone sends it, at any time, also while the hold runs and for
|
|
1963
|
+
* a loan before it is due, for no project fee. Nothing moves and `to` is not read; the hold's rent goes to whoever paid
|
|
1964
|
+
* it, and a loan's debt ends unpaid. Its receipt is `NftGone`, with no `Released`.
|
|
1955
1965
|
*/
|
|
1956
1966
|
export function releaseIx({
|
|
1957
1967
|
signer,
|
|
@@ -2480,6 +2490,8 @@ const treasuryAccounts = (treasury, mint, programs) => walletAccounts(treasury,
|
|
|
2480
2490
|
* close, and the next carries on. Every pull of one entry's places names the same `nonce`: a place's roll is made at
|
|
2481
2491
|
* `prizeRollNonce(nonce + place)`, so a later pull with another nonce can land on an order an earlier pull made and fail
|
|
2482
2492
|
* ("already in use"). js/play names `prizeNonce(round, entry)` for every pull of an entry, whoever sends it.
|
|
2493
|
+
* An NFT ticket proven gone from the project (`ticket`, as `piece` takes it) goes nowhere: its part settles and
|
|
2494
|
+
* nothing moves (`LotteryPulled` with amount 0 beside `NftGone`).
|
|
2483
2495
|
*/
|
|
2484
2496
|
export function pullIx({
|
|
2485
2497
|
payer,
|
|
@@ -2739,14 +2751,17 @@ export function endsToWalk(farm, ends, now) {
|
|
|
2739
2751
|
return ends.filter(e => e.at > clock && e.at <= stop).length;
|
|
2740
2752
|
}
|
|
2741
2753
|
|
|
2742
|
-
/** How many ends `walk_farm` walks in one transaction at `WALK_COMPUTE_UNITS`: an end takes
|
|
2743
|
-
* halving rate (`js/test/farm-ends.test.mjs`
|
|
2744
|
-
|
|
2754
|
+
/** How many ends `walk_farm` walks in one transaction at `WALK_COMPUTE_UNITS`: an end takes up to some 3,150 units at
|
|
2755
|
+
* the dearest farm, a halving rate and seats of 4 × 10^16 base units at a ×100 tier (`js/test/farm-ends.test.mjs`
|
|
2756
|
+
* measures it: 400 ends take 1,260,730 units, 2,270 an end with light seats). Since loot 0.27 each end walked also
|
|
2757
|
+
* frees the leaving weight's share of the carry, some 300 units more at that farm; it was 500 until then, measured
|
|
2758
|
+
* with light seats only. */
|
|
2759
|
+
export const WALK_PER_TX = 400;
|
|
2745
2760
|
/** The compute units a transaction that walks ends asks for: `walk_farm`, or a move with ends to walk first. */
|
|
2746
2761
|
export const WALK_COMPUTE_UNITS = 1_400_000;
|
|
2747
|
-
/** How many ends a move can walk first at `WALK_COMPUTE_UNITS` and still do its own work
|
|
2748
|
-
* first (`farmNeedsWalk`). */
|
|
2749
|
-
export const MOVE_WALK_ROOM =
|
|
2762
|
+
/** How many ends a move can walk first at `WALK_COMPUTE_UNITS` and still do its own work (some 400,000 units are left
|
|
2763
|
+
* for it at the dearest farm): past this, the farm is walked first (`farmNeedsWalk`). 400 until loot 0.27. */
|
|
2764
|
+
export const MOVE_WALK_ROOM = 300;
|
|
2750
2765
|
|
|
2751
2766
|
/**
|
|
2752
2767
|
* Whether a farm has passed more ends than a move can walk in its own transaction (`endsToWalk`, `MOVE_WALK_ROOM`): such
|
|
@@ -2919,6 +2934,11 @@ const NO_PIECE = {
|
|
|
2919
2934
|
* token account of `wallet` (its holder, or whoever the project sends it to) and the project's own;
|
|
2920
2935
|
* a compressed NFT (`compressed`, from `compressedFromDas`), proven by a `proveLeafIx` earlier in the transaction.
|
|
2921
2936
|
* A Token Metadata or compressed NFT passes its Sheet: the project writes nothing on it.
|
|
2937
|
+
* An NFT proven gone from the project (whoever controls its collection took it out of the project's hands or burned
|
|
2938
|
+
* it) is let go by the record that holds it, on these same accounts (loot 0.27, receipt `NftGone`): a Core asset shows
|
|
2939
|
+
* it by itself; a compressed NFT by its record, proven again as the leaf is now; a Token Metadata NFT by
|
|
2940
|
+
* `tokenMetadata.evidence`, the token account that holds it now, or its mint when it was burned, which goes in the
|
|
2941
|
+
* place of `wallet`'s token account. Anything else shows nothing, and the route goes its usual way.
|
|
2922
2942
|
* What the route does with it, so only the accounts it takes are passed (each true when left out):
|
|
2923
2943
|
* `moves`: hands it to the project or sends it on; `locks`: locks or unlocks it in its wallet; `writes`: writes on it;
|
|
2924
2944
|
* `burns`: burns a Core asset (false when left out); `reads`: reads its Sheet; `held`: reads it in the project's token
|
|
@@ -2963,7 +2983,8 @@ export function piece({
|
|
|
2963
2983
|
}
|
|
2964
2984
|
if (tm) {
|
|
2965
2985
|
// `tm.into`: the token account of `wallet`'s the NFT goes into when it is not their associated one (`freshNftAccount`).
|
|
2966
|
-
|
|
2986
|
+
// `tm.evidence`: what shows the NFT gone from the project (the account that holds it now, or its mint), in its place.
|
|
2987
|
+
const token = tm.evidence ?? tm.into ?? ata(wallet, asset, TOKEN_PROGRAM);
|
|
2967
2988
|
const projectToken = ata(project, asset, TOKEN_PROGRAM);
|
|
2968
2989
|
const pnft = Boolean(tm.programmable);
|
|
2969
2990
|
// Token Metadata is called to lock it or move it; a move also takes the project's token account (and record) and the
|
|
@@ -4000,6 +4021,10 @@ export function holdIx({
|
|
|
4000
4021
|
* by, and gets back the rent it no longer needs. `drop`: the NFT leaves the stash and stays where it is, owed to the
|
|
4001
4022
|
* treasury in its hold (`["hold", nft, 0]`, the signer paying its rent until anyone `release`s it to the treasury, one
|
|
4002
4023
|
* NFT a call). `out` is never the project's own address nor another account of the program (`ToTheProject`).
|
|
4024
|
+
* Taking out of a project's stash an NFT proven gone from the project (`piece`: the Core asset itself,
|
|
4025
|
+
* `tokenMetadata.evidence`, a compressed NFT's record proven again) only takes its row off the list: anyone may send
|
|
4026
|
+
* it (`signer`), for no fee, nothing moves and no hold is made (`NftGone`, no `Stashed`); the rent the stash no longer
|
|
4027
|
+
* needs goes to the signer only when it is one of the project's keys. A lottery's own stash stays the project's keys'.
|
|
4003
4028
|
*/
|
|
4004
4029
|
export function stashIx({
|
|
4005
4030
|
signer,
|
|
@@ -4922,6 +4947,10 @@ export function openListingIx({
|
|
|
4922
4947
|
* takes none of the NFT's accounts but its address. `holders`, `programs`, `treasury` as for any split. After the
|
|
4923
4948
|
* take's own accounts come a sale's compressed NFT's proof, then the shares' accounts, then (in `extra`) Core's
|
|
4924
4949
|
* transfer adapters and any transfer hook's accounts, which the program finds by address after the shares'.
|
|
4950
|
+
* A listing whose NFT is proven gone from the project (`piece`: the Core asset itself, `tokenMetadata.evidence`, a
|
|
4951
|
+
* compressed NFT's record proven again) closes instead, whoever sends this: the taker pays nothing and no fee, the
|
|
4952
|
+
* listing's rent goes to its maker, and the receipt is `NftGone`. It is refused while a rental runs (`Rented`), and
|
|
4953
|
+
* closes once the rental has run.
|
|
4925
4954
|
*/
|
|
4926
4955
|
export function takeListingIx({
|
|
4927
4956
|
taker,
|
|
@@ -4958,7 +4987,10 @@ export function takeListingIx({
|
|
|
4958
4987
|
...piece({ project, asset: listing.asset, wallet: taker, compressed, ...ONLY_MOVES, moves: false }),
|
|
4959
4988
|
proof: [],
|
|
4960
4989
|
}
|
|
4961
|
-
:
|
|
4990
|
+
: tm?.evidence
|
|
4991
|
+
? // A rented Token Metadata NFT shown gone: the project's token account of it, and the one that holds it now.
|
|
4992
|
+
heldPiece({ project, asset: listing.asset, tokenMetadata: tm, evidence: tm.evidence })
|
|
4993
|
+
: standIn(listing.asset);
|
|
4962
4994
|
return lootIx(
|
|
4963
4995
|
'take_listing',
|
|
4964
4996
|
{ terms: listingTerms(listing), periods },
|
|
@@ -4989,7 +5021,10 @@ export function takeListingIx({
|
|
|
4989
5021
|
|
|
4990
5022
|
/**
|
|
4991
5023
|
* Its maker closes a listing (`listing` the decoded Listing): the NFT goes home. While a rental runs it only stops (no
|
|
4992
|
-
* renter takes it again), and the same call once the rental is over takes it home. Free.
|
|
5024
|
+
* renter takes it again), and the same call once the rental is over takes it home. Free. A listing whose NFT is proven
|
|
5025
|
+
* gone from the project (`piece`: the Core asset itself, `tokenMetadata.evidence`, a compressed NFT's record proven
|
|
5026
|
+
* again) closes without it once no rental runs, its rent to its maker (`NftGone`, no `Unlisted`); anyone else closes
|
|
5027
|
+
* such a listing with `takeListingIx`.
|
|
4993
5028
|
*/
|
|
4994
5029
|
export function closeListingIx({
|
|
4995
5030
|
maker,
|
package/js/economy/check.mjs
CHANGED
|
@@ -58,6 +58,7 @@ import {
|
|
|
58
58
|
utf8Bytes,
|
|
59
59
|
wholeCount,
|
|
60
60
|
weights,
|
|
61
|
+
tokenLabels,
|
|
61
62
|
} from './model.mjs';
|
|
62
63
|
// plan.mjs imports check() in turn: each module only calls the other's functions, never at load time.
|
|
63
64
|
import { boxArgs, boxOrderBytes, placeholderContext, recipeArgs, setupSteps, stepAloneBytes } from './plan.mjs';
|
|
@@ -1017,8 +1018,15 @@ export function check(
|
|
|
1017
1018
|
// A token the setup makes is named on chain as it is made (its Token Metadata record), and the record takes so
|
|
1018
1019
|
// many bytes of each, not characters: a longer one would be refused on chain, in the middle of going live. One
|
|
1019
1020
|
// with no name or ticker at all would be written blank, and show as an unknown token for the record's price. A
|
|
1020
|
-
// token that is live
|
|
1021
|
-
|
|
1021
|
+
// token that is live keeps its words as labels, held to nothing, until they are changed: a change is written to
|
|
1022
|
+
// its record, so the new words are held to what a record takes.
|
|
1023
|
+
const liveAs = economy.live?.mints?.[t.key]
|
|
1024
|
+
? (economy.live.published?.tokens ?? []).find(x => x.key === t.key)
|
|
1025
|
+
: null;
|
|
1026
|
+
const written =
|
|
1027
|
+
!economy.live?.mints?.[t.key] ||
|
|
1028
|
+
(liveAs && JSON.stringify(tokenLabels(liveAs)) !== JSON.stringify(tokenLabels(t)));
|
|
1029
|
+
if (source === 'game' && written)
|
|
1022
1030
|
for (const [field, what] of [
|
|
1023
1031
|
['name', 'name'],
|
|
1024
1032
|
['symbol', 'ticker'],
|
|
@@ -1468,8 +1476,8 @@ export function check(
|
|
|
1468
1476
|
warn(
|
|
1469
1477
|
'W-OUTSIDE-NFT',
|
|
1470
1478
|
`${where}.collection`,
|
|
1471
|
-
`${what} takes NFTs of ${r.collection}, a collection the project does not control. The creator of an NFT with a permanent delegate can pull it back out of a hold, a stake, a loan or a listing, and a record
|
|
1472
|
-
'Before a player deposits one, the app
|
|
1479
|
+
`${what} takes NFTs of ${r.collection}, a collection the project does not control. The creator of an NFT with a permanent delegate can pull it back out of a hold, a stake, a loan or a listing, or burn it there: the record then closes without it (anyone may close it, and nothing is paid: a lender is out the loan). A record nobody closed can still take the next owner’s deposit of the same NFT. This holds for every rule on such a collection in the spec.`,
|
|
1480
|
+
'Before a player deposits one, the app closes this project’s records an earlier owner left of the NFT (a listing, a hold), in the same transaction; prefer collections without a permanent delegate.',
|
|
1473
1481
|
);
|
|
1474
1482
|
}
|
|
1475
1483
|
// A gate may list wallets instead (rules.rs Gate::Wallets): a duel's two players, a tournament's entrants.
|
package/js/economy/cost.mjs
CHANGED
|
@@ -39,9 +39,10 @@ import { inAll, round, simulate } from './simulate.mjs';
|
|
|
39
39
|
const NO_OPTIONS = Object.freeze(/** @type {Record<string, any>} */ ({}));
|
|
40
40
|
|
|
41
41
|
const sol = lamports => round(Number(lamports) / Number(LAMPORTS_PER_SOL));
|
|
42
|
-
/** Setup steps that are no loot instruction, and so charge no fee: a token minted,
|
|
43
|
-
* grant's funds. Every other step is the owner or the authority calling loot, which
|
|
44
|
-
|
|
42
|
+
/** Setup steps that are no loot instruction, and so charge no fee: a token minted, a token's name written again, the
|
|
43
|
+
* collection made, SOL put in a grant's funds. Every other step is the owner or the authority calling loot, which
|
|
44
|
+
* charges the project fee. */
|
|
45
|
+
const NO_FEE_STEP = /^(token:|name:|collection$|fund:|rent:)/;
|
|
45
46
|
/** Setup cost from a plan (plan.mjs `plan`). */
|
|
46
47
|
export function setupCost(plan, economy) {
|
|
47
48
|
const m = model(economy);
|
|
@@ -473,8 +474,8 @@ export function actionCosts(economy, { now } = NO_OPTIONS) {
|
|
|
473
474
|
|
|
474
475
|
/** Setup and play costs for a spec, or its refusals. */
|
|
475
476
|
/** `only` and `live`: a change to a live economy, priced as only what it publishes (live.mjs `changedParts`). */
|
|
476
|
-
export function cost(economy, { now = Math.floor(Date.now() / 1000), only = null, live = null } = {}) {
|
|
477
|
-
const plan = compile(economy, { now, only, live });
|
|
477
|
+
export function cost(economy, { now = Math.floor(Date.now() / 1000), only = null, live = null, renames = null } = {}) {
|
|
478
|
+
const plan = compile(economy, { now, only, live, renames });
|
|
478
479
|
if (!plan.ok) return plan;
|
|
479
480
|
return {
|
|
480
481
|
ok: true,
|
package/js/economy/explain.mjs
CHANGED
|
@@ -205,14 +205,15 @@ export function explainEconomy(economy, { now = Math.floor(Date.now() / 1000) }
|
|
|
205
205
|
if (economy.prompt) lines.push(`Asked for: “${economy.prompt}”`);
|
|
206
206
|
|
|
207
207
|
// A token the setup makes is named on chain as it is made (plan.mjs): what wallets will show, and that a picture
|
|
208
|
-
// needs its link before going live. One that is live already keeps what it has
|
|
208
|
+
// needs its link before going live. One that is live already keeps what it has until the spec changes its name,
|
|
209
|
+
// which is written at the next approve (live.mjs changedParts, approve/live-check.mjs readRenames).
|
|
209
210
|
section(
|
|
210
211
|
'The tokens going live makes',
|
|
211
212
|
[...m.tokens.values()]
|
|
212
213
|
.filter(t => t.source === 'game' && !economy.live?.mints?.[t.key])
|
|
213
214
|
.map(
|
|
214
215
|
t =>
|
|
215
|
-
`${t.symbol}${t.name !== t.symbol ? ` (“${t.name}”)` : ''}: ${t.fixedSupply ? `a fixed supply of ${num(t.supply)}, all of it to your wallet, and no more ever` : 'minted by the project as its rules say'}. It is named on chain as it is made, so wallets show ${t.uri ? 'its name, its ticker and the picture its link gives' : 'its name and ticker and no picture: for one, give it a `uri` (a link to a JSON file that names its `image`) before going live'}. Only your wallet can change the name, ticker and link later
|
|
216
|
+
`${t.symbol}${t.name !== t.symbol ? ` (“${t.name}”)` : ''}: ${t.fixedSupply ? `a fixed supply of ${num(t.supply)}, all of it to your wallet, and no more ever` : 'minted by the project as its rules say'}. It is named on chain as it is made, so wallets show ${t.uri ? 'its name, its ticker and the picture its link gives' : 'its name and ticker and no picture: for one, give it a `uri` (a link to a JSON file that names its `image`) before going live'}. Only your wallet can change the name, ticker and link later: change them here and approve again.`,
|
|
216
217
|
),
|
|
217
218
|
);
|
|
218
219
|
|
package/js/economy/guide.mjs
CHANGED
|
@@ -10,7 +10,7 @@ const sol = lamports => Number(lamports) / Number(LAMPORTS_PER_SOL);
|
|
|
10
10
|
export const PARTS = {
|
|
11
11
|
tokens: {
|
|
12
12
|
def: 'token',
|
|
13
|
-
says: 'The currencies: SOL, any existing mint by address (classic or Token-2022, coins from a token launchpad or an AMM included), or a token the game makes when it goes live: one the game mints as its rules say (points, tickets, shards, an emission), or one with a fixed `supply` minted once to the owner with minting turned off for good (a community token you keep). A token the game makes is named on chain as it is made (`name`, `symbol`, and `uri`: a link to a JSON file naming its picture), which is what wallets show; only the owner’s wallet can change that later,
|
|
13
|
+
says: 'The currencies: SOL, any existing mint by address (classic or Token-2022, coins from a token launchpad or an AMM included), or a token the game makes when it goes live: one the game mints as its rules say (points, tickets, shards, an emission), or one with a fixed `supply` minted once to the owner with minting turned off for good (a community token you keep). A token the game makes is named on chain as it is made (`name`, `symbol`, and `uri`: a link to a JSON file naming its picture), which is what wallets show; only the owner’s wallet can change that later, by changing them in the spec and approving again. loot does not put a token on a market: for trading, launch it on a token launchpad or an AMM, then name the mint. `xsolana token <mint>` (xsolana_token) reads any existing mint into its entry, with its decimals, program and `powers` (what its own keys can do), so none of it is typed by hand.',
|
|
14
14
|
acts: 'nobody: a token is a fact',
|
|
15
15
|
},
|
|
16
16
|
currency: {
|
|
@@ -191,7 +191,7 @@ export const CANNOT = [
|
|
|
191
191
|
words: 'lend lending borrow loan leverage perp perpetual options vault yield strategy collateral liquidation',
|
|
192
192
|
why: 'there are no price oracles or liquidations in loot: its only loan is a fixed-term loan against an NFT',
|
|
193
193
|
nearest: [
|
|
194
|
-
'a fixed-term loan against an NFT between players, with no trust in the game: a lender opens an offer with `deal: { Lend: { amount, repay, lasts } }` for any NFT that passes a rule; a holder borrows (`borrowIx`: the NFT held by the project, `amount` to them); anyone repays `repay` before it is due (`repayIx`: the money to the lender and the NFT home to the borrower in the same step; with `home: false` the money alone, for an NFT that can’t move right now, and anyone sends it home later with `releaseIx`); no repayment is taken for an NFT its collection took back or burned; unpaid once due, anyone sends it to the lender (`releaseIx` with `to` the lender). No grace period, no partial repayment, no cut of the interest; the lender trusts the NFT’s collection not to block or pull back the NFT. The borrower trusts the loan’s token: whoever holds its freeze authority, a pause, a transfer hook or a permanent delegate can make repaying fail, and a lender who holds one can take the NFT when it is due (the program takes such tokens; `app.loanOffer` names each power and its holder, and `app.borrow` needs `acceptRisks`): lend in SOL or a plain token. Made from the game’s page at run time, not in xsolana.json',
|
|
194
|
+
'a fixed-term loan against an NFT between players, with no trust in the game: a lender opens an offer with `deal: { Lend: { amount, repay, lasts } }` for any NFT that passes a rule; a holder borrows (`borrowIx`: the NFT held by the project, `amount` to them); anyone repays `repay` before it is due (`repayIx`: the money to the lender and the NFT home to the borrower in the same step; with `home: false` the money alone, for an NFT that can’t move right now, and anyone sends it home later with `releaseIx`); no repayment is taken for an NFT its collection took back or burned (anyone then closes that loan’s hold for nothing, also before it is due: the borrower keeps what they borrowed and the lender is out it; `app.claims()` lists it for both); unpaid once due, anyone sends it to the lender (`releaseIx` with `to` the lender). No grace period, no partial repayment, no cut of the interest; the lender trusts the NFT’s collection not to block or pull back the NFT. The borrower trusts the loan’s token: whoever holds its freeze authority, a pause, a transfer hook or a permanent delegate can make repaying fail, and a lender who holds one can take the NFT when it is due (the program takes such tokens; `app.loanOffer` names each power and its holder, and `app.borrow` needs `acceptRisks`): lend in SOL or a plain token. Made from the game’s page at run time, not in xsolana.json',
|
|
195
195
|
'a farm whose rewards the game sends into its vault (staking rewards, not interest)',
|
|
196
196
|
'otherwise nothing in loot: say so plainly (leverage, perps and options need a protocol built for them)',
|
|
197
197
|
],
|
package/js/economy/live.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { encodeBase58 } from '../chain/base58.js';
|
|
4
4
|
import { randomBytes } from '../chain/bytes.js';
|
|
5
5
|
import { LOOT_PROGRAM } from '../client/pda.js';
|
|
6
|
-
import { compressedTree, listOf, model } from './model.mjs';
|
|
6
|
+
import { compressedTree, listOf, model, tokenLabels } from './model.mjs';
|
|
7
7
|
import { auctionSeller, plan, setupContext } from './plan.mjs';
|
|
8
8
|
|
|
9
9
|
/** A key for each fresh account the plan creates. Their secret halves are made by whoever signs (the page). */
|
|
@@ -17,12 +17,12 @@ export function freshAddresses(names, random = randomBytes) {
|
|
|
17
17
|
*/
|
|
18
18
|
export function prepare(
|
|
19
19
|
economy,
|
|
20
|
-
{ owner, keys, live = null, now = Math.floor(Date.now() / 1000), rent, only = null, slot = null },
|
|
20
|
+
{ owner, keys, live = null, now = Math.floor(Date.now() / 1000), rent, only = null, slot = null, renames = null },
|
|
21
21
|
) {
|
|
22
22
|
const m = model(economy, { now });
|
|
23
23
|
// `slot`: a recent slot (in the slot hashes now), which the project's standing lookup table is made at.
|
|
24
24
|
const ctx = { ...setupContext(m, { owner, keys, live: live ?? {}, rent }), slot };
|
|
25
|
-
const p = plan(economy, { now, ctx, live, only });
|
|
25
|
+
const p = plan(economy, { now, ctx, live, only, renames });
|
|
26
26
|
if (!p.ok) return p;
|
|
27
27
|
// Going live always makes the project's standing lookup table (the plan's "table" step). Without a recent slot it
|
|
28
28
|
// can't be made: refused when a part's widest transaction needs it (`wideParts`), so no player is left with a
|
|
@@ -133,10 +133,11 @@ const FIXED = new Set(['farm', 'pool', 'auction']);
|
|
|
133
133
|
*/
|
|
134
134
|
export function changedParts(economy) {
|
|
135
135
|
const published = economy.live?.published;
|
|
136
|
-
if (!published) return { only: null, fixed: [], removed: [], settings: [] };
|
|
136
|
+
if (!published) return { only: null, fixed: [], removed: [], settings: [], renames: [] };
|
|
137
137
|
const only = new Set();
|
|
138
138
|
const fixed = [];
|
|
139
139
|
const removed = [];
|
|
140
|
+
const renames = [];
|
|
140
141
|
// A grant's `trust` is the spec's own note about its server, never sent on chain: changing it publishes nothing.
|
|
141
142
|
const strip = ({ id, index, shopBox, seller, trust, ...rest }) => JSON.stringify(rest);
|
|
142
143
|
for (const [list, kind] of Object.entries(PART_LISTS)) {
|
|
@@ -155,8 +156,20 @@ export function changedParts(economy) {
|
|
|
155
156
|
}
|
|
156
157
|
for (const key of before.keys()) removed.push(`${kind}:${key}`);
|
|
157
158
|
}
|
|
158
|
-
|
|
159
|
-
|
|
159
|
+
// A token is made once. One whose name, ticker or link the spec now gives otherwise than it was published with
|
|
160
|
+
// (`renames`; by what they come to, not how they are spelled: model.mjs tokenLabels) has its name record written
|
|
161
|
+
// again, when it has one its owner can still change: that is read from the chain as the change is approved
|
|
162
|
+
// (approve/live-check.mjs readRenames), never taken from this file.
|
|
163
|
+
for (const t of economy.tokens ?? []) {
|
|
164
|
+
const was = (published.tokens ?? []).find(x => x.key === t.key);
|
|
165
|
+
if (!was) only.add(`token:${t.key}`);
|
|
166
|
+
else if (
|
|
167
|
+
['project', 'game'].includes(t.source) &&
|
|
168
|
+
economy.live.mints?.[t.key] &&
|
|
169
|
+
JSON.stringify(tokenLabels(was)) !== JSON.stringify(tokenLabels(t))
|
|
170
|
+
)
|
|
171
|
+
renames.push(t.key);
|
|
172
|
+
}
|
|
160
173
|
// The first compressed prize opens the project's tree.
|
|
161
174
|
if (!economy.live.tree && compressedTree(model(economy))) only.add('tree');
|
|
162
175
|
// A traits root set again replaces the last.
|
|
@@ -168,5 +181,12 @@ export function changedParts(economy) {
|
|
|
168
181
|
if ((published.treasury ?? null) !== (economy.treasury ?? null)) settings.push('its treasury');
|
|
169
182
|
if (JSON.stringify(published.collections ?? []) !== JSON.stringify(economy.collections ?? []))
|
|
170
183
|
settings.push('the collections it takes');
|
|
171
|
-
return { only, fixed, removed, settings };
|
|
184
|
+
return { only, fixed, removed, settings, renames };
|
|
172
185
|
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* In words, for a tool that does not read the chain: the spec changes what these tokens are called, and what becomes
|
|
189
|
+
* of that is decided when the change is approved.
|
|
190
|
+
*/
|
|
191
|
+
export const renamesNote = renames =>
|
|
192
|
+
`The spec changes what ${renames.join(', ')} ${renames.length === 1 ? 'is' : 'are'} called. A token’s name on chain is its name record: approving reads it, and writes the new name, ticker and link there when the token has a record its owner can still change (and nothing when the record says them already). A token that went live before setups named tokens has no record and can’t be given one, and a record handed on, made unchangeable or given a royalty outside these tools is left as it is: the approval page says so, sends nothing for that token, and the change stays listed here until the spec gives its old name back.`;
|
package/js/economy/model.mjs
CHANGED
|
@@ -392,6 +392,16 @@ export const spendOf = (then, m) => {
|
|
|
392
392
|
return { Give: [then.giveTo] };
|
|
393
393
|
};
|
|
394
394
|
|
|
395
|
+
/**
|
|
396
|
+
* What a token the setup makes is called on chain, from its entry in a spec as written: [name, ticker, link], with
|
|
397
|
+
* the names it gets when the spec gives none (`tokenOf`: its key in capitals for a ticker, its ticker or key for a
|
|
398
|
+
* name). Two entries with the same labels write the same record, however they are spelled.
|
|
399
|
+
*/
|
|
400
|
+
export function tokenLabels(t) {
|
|
401
|
+
const symbol = t.symbol ?? String(t.key).toUpperCase().slice(0, 10);
|
|
402
|
+
return [t.name ?? t.symbol ?? t.key, symbol, t.uri ?? ''];
|
|
403
|
+
}
|
|
404
|
+
|
|
395
405
|
/** What a token is on chain: its mint (or "@mint:<key>" until it exists), program, decimals, and who can mint it. */
|
|
396
406
|
function tokenOf(given) {
|
|
397
407
|
// "project" is the spec's word for a token the project mints; "game" is the older spelling of it.
|
|
@@ -408,10 +418,11 @@ function tokenOf(given) {
|
|
|
408
418
|
gameMints: false,
|
|
409
419
|
};
|
|
410
420
|
const decimals = t.decimals ?? (t.source === 'game' ? 0 : undefined);
|
|
421
|
+
const [name, symbol] = tokenLabels(t);
|
|
411
422
|
return {
|
|
412
423
|
...t,
|
|
413
|
-
symbol
|
|
414
|
-
name
|
|
424
|
+
symbol,
|
|
425
|
+
name,
|
|
415
426
|
decimals,
|
|
416
427
|
program: t.program ?? 'spl-token',
|
|
417
428
|
mint: t.mint ?? `@mint:${t.key}`,
|
package/js/economy/plan.mjs
CHANGED
|
@@ -21,7 +21,13 @@ import {
|
|
|
21
21
|
TOKEN_PROGRAM,
|
|
22
22
|
} from '../chain/pda.js';
|
|
23
23
|
import { createTableIx, extendTableIx } from '../chain/lookup.js';
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
createFungibleMetadataIx,
|
|
26
|
+
METADATA_BYTES,
|
|
27
|
+
METADATA_FEE,
|
|
28
|
+
METADATA_LIMITS,
|
|
29
|
+
updateFungibleMetadataIx,
|
|
30
|
+
} from '../chain/metadata.js';
|
|
25
31
|
import { MEMO_PROGRAM } from '../chain/token2022.js';
|
|
26
32
|
import {
|
|
27
33
|
computeUnitLimit,
|
|
@@ -78,6 +84,7 @@ import {
|
|
|
78
84
|
weights,
|
|
79
85
|
wholeCount,
|
|
80
86
|
windowOf,
|
|
87
|
+
tokenLabels,
|
|
81
88
|
} from './model.mjs';
|
|
82
89
|
import { BUDGET } from './txsize.mjs';
|
|
83
90
|
|
|
@@ -723,7 +730,7 @@ const placeholderKey = name => encodeBase58(sha256(utf8(`loot-plan:${name}`)));
|
|
|
723
730
|
* seats there as it is released, its clock stopped while nobody is in. A seed that lands
|
|
724
731
|
* before the first stake or after it is released the same way from that stake on, so seeding first would change nothing.
|
|
725
732
|
*/
|
|
726
|
-
export function setupSteps(economy, { now = 0, only = null, live: given = null } = {}) {
|
|
733
|
+
export function setupSteps(economy, { now = 0, only = null, live: given = null, renames = null } = {}) {
|
|
727
734
|
const m = model(economy, { now });
|
|
728
735
|
const live = madeSoFar(given);
|
|
729
736
|
const steps = [];
|
|
@@ -731,19 +738,51 @@ export function setupSteps(economy, { now = 0, only = null, live: given = null }
|
|
|
731
738
|
const landed = new Set(live?.landed ?? []);
|
|
732
739
|
const add = step => want(step.part) && !landed.has(step.id) && steps.push({ creates: [], signers: [owner], ...step });
|
|
733
740
|
const project = ctx => ctx.project;
|
|
734
|
-
|
|
741
|
+
// The project's own first steps (its collection, the project, the accounts it pays into, its lookup table) belong to
|
|
742
|
+
// a new setup, and to one cut short before the file was recorded as live (`partial`, with nothing `published`): gone
|
|
743
|
+
// on with, whichever of them has not landed is still made. Before, a setup cut short after the project's step never
|
|
744
|
+
// made the rest.
|
|
745
|
+
const fresh = !live?.project || Boolean(given?.partial && !given?.published);
|
|
735
746
|
|
|
736
747
|
// Tokens the project mints: a classic mint with the owner as its authority, handed to the project right after.
|
|
737
748
|
// Each is named on chain in the same step (its Token Metadata record: the name, the ticker and the link wallets
|
|
738
749
|
// show), while the owner is still its mint authority: Token Metadata takes a record from nobody else, so a mint whose
|
|
739
750
|
// minting was handed on or turned off without one can never have one. The owner stays the record's update authority.
|
|
740
751
|
for (const t of m.tokens.values()) {
|
|
752
|
+
// A live token whose name record is to be written again (`renames`: its key; the approval reads the chain for
|
|
753
|
+
// which, live-check.mjs readRenames): the new name, ticker and link, signed by the owner, the record's update
|
|
754
|
+
// authority. Not a part a change scopes (`only`), and never held back as landed: the chain says when it is done.
|
|
755
|
+
const labels = [t.name, t.symbol, t.uri ?? ''];
|
|
756
|
+
const writable =
|
|
757
|
+
labels.every(
|
|
758
|
+
(v, i) => utf8(String(v)).length <= [METADATA_LIMITS.name, METADATA_LIMITS.symbol, METADATA_LIMITS.uri][i],
|
|
759
|
+
) &&
|
|
760
|
+
String(t.name).trim() !== '' &&
|
|
761
|
+
String(t.symbol).trim() !== '';
|
|
762
|
+
if (t.source === 'game' && live?.mints?.[t.key] && renames && new Set(renames).has(t.key) && writable)
|
|
763
|
+
steps.push({
|
|
764
|
+
creates: [],
|
|
765
|
+
signers: [owner],
|
|
766
|
+
id: `name:${t.key}`,
|
|
767
|
+
part: `name:${t.key}`,
|
|
768
|
+
// (named by the ticker it was published with: the new one is what it is changed to)
|
|
769
|
+
says: `Change what the token ${tokenLabels((given?.published?.tokens ?? []).find(x => x.key === t.key) ?? t)[1]} (mint ${live.mints[t.key]}) is called on chain to “${t.name}”, ${t.symbol}, ${t.uri ? `with the link ${t.uri}` : 'no picture link'}: its name record is written again, and stays only your wallet’s to change`,
|
|
770
|
+
build: ctx => [
|
|
771
|
+
updateFungibleMetadataIx({
|
|
772
|
+
mint: ctx.mints[t.key],
|
|
773
|
+
authority: ctx.owner,
|
|
774
|
+
name: t.name,
|
|
775
|
+
symbol: t.symbol,
|
|
776
|
+
uri: t.uri ?? '',
|
|
777
|
+
}),
|
|
778
|
+
],
|
|
779
|
+
});
|
|
741
780
|
if (t.source !== 'game' || live?.mints?.[t.key] || (t.mint && !t.mint.startsWith('@'))) continue;
|
|
742
781
|
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,
|
|
782
|
+
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, and Token Metadata keeps ${Number(METADATA_FEE) / 1e9} SOL for it)`;
|
|
744
783
|
const record = { what: `the name of ${t.symbol} on chain`, bytes: METADATA_BYTES, fee: Number(METADATA_FEE) };
|
|
745
784
|
// 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
|
|
785
|
+
// that is live already and whose words were not changed. Such a token is made here only when a live economy is
|
|
747
786
|
// planned as if it were new (explain's cost of the whole setup, which is never sent): its record is left out.
|
|
748
787
|
const fits = ['name', 'symbol', 'uri'].every(f => utf8(String(t[f] ?? '')).length <= METADATA_LIMITS[f]);
|
|
749
788
|
const name = ctx =>
|
|
@@ -926,26 +965,39 @@ export function setupSteps(economy, { now = 0, only = null, live: given = null }
|
|
|
926
965
|
// the first of its addresses, then extended a group a step.
|
|
927
966
|
if (fresh) {
|
|
928
967
|
const named = standingAddresses(m);
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
:
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
968
|
+
// A table whose making landed with no address kept (a file an earlier release left, which recorded a transaction
|
|
969
|
+
// found landed without what it made) can't be added to: its address is of a slot nobody recorded. It stays as it
|
|
970
|
+
// is, and nothing more is planned for it.
|
|
971
|
+
const lost = landed.has('table') && !given?.tables?.[0];
|
|
972
|
+
groupsOf(named, TABLE_GROUP).forEach(
|
|
973
|
+
(group, i) =>
|
|
974
|
+
(i && lost) ||
|
|
975
|
+
add({
|
|
976
|
+
id: i ? `table:${i}` : 'table',
|
|
977
|
+
part: 'project',
|
|
978
|
+
says: i
|
|
979
|
+
? `Add ${group.length} more addresses to the project’s lookup table`
|
|
980
|
+
: `Make the project’s lookup table of the ${named.length} addresses its transactions name most, so a large one takes one approval`,
|
|
981
|
+
creates: i
|
|
982
|
+
? [
|
|
983
|
+
{
|
|
984
|
+
what: `room for ${group.length} addresses in the lookup table`,
|
|
985
|
+
bytes: 32 * group.length,
|
|
986
|
+
grows: true,
|
|
987
|
+
},
|
|
988
|
+
]
|
|
989
|
+
: [{ what: 'the project’s lookup table', bytes: 56 + 32 * group.length }],
|
|
990
|
+
build: ctx => {
|
|
991
|
+
const addresses = group.map(at => at(ctx)).filter(Boolean);
|
|
992
|
+
// A table made already (its first step landed in an earlier batch, or before the setup was cut short) is the
|
|
993
|
+
// one the rest is added to: its address is of the slot it was made at, never of this batch's.
|
|
994
|
+
if (i && ctx.tables?.[0]) return [extendTableIx({ table: ctx.tables[0], authority: ctx.owner, addresses })];
|
|
995
|
+
// With no recent slot known (a plan built offline), there is no table to make.
|
|
996
|
+
if (ctx.slot == null) return [];
|
|
997
|
+
const { address: table, instruction } = createTableIx({ authority: ctx.owner, slot: ctx.slot });
|
|
998
|
+
return [...(i ? [] : [instruction]), extendTableIx({ table, authority: ctx.owner, addresses })];
|
|
999
|
+
},
|
|
1000
|
+
}),
|
|
949
1001
|
);
|
|
950
1002
|
}
|
|
951
1003
|
|
|
@@ -1809,6 +1861,8 @@ export function setupContext(m, { owner: ownerKey, keys, live: given = {}, rent
|
|
|
1809
1861
|
project: own,
|
|
1810
1862
|
treasury: live.treasury ?? m.treasuryWallet ?? ownerKey,
|
|
1811
1863
|
mints,
|
|
1864
|
+
// the project's lookup table, once its first step landed
|
|
1865
|
+
tables: live.tables ?? null,
|
|
1812
1866
|
keys,
|
|
1813
1867
|
rent,
|
|
1814
1868
|
};
|
|
@@ -1876,11 +1930,14 @@ export function pack(steps, ctx) {
|
|
|
1876
1930
|
* The plan for a spec: refusals, or every step packed into transactions. With `ctx` the transactions carry the
|
|
1877
1931
|
* real instructions; without it they are built on stand-in addresses (for sizing, cost and showing).
|
|
1878
1932
|
*/
|
|
1879
|
-
export function plan(
|
|
1933
|
+
export function plan(
|
|
1934
|
+
economy,
|
|
1935
|
+
{ now = Math.floor(Date.now() / 1000), ctx = null, only = null, live = null, renames = null } = {},
|
|
1936
|
+
) {
|
|
1880
1937
|
// With a real context the project is known: a box's write is checked with its own address searches counted.
|
|
1881
1938
|
const checked = check(economy, { now, ...(ctx?.project ? { project: ctx.project } : {}) });
|
|
1882
1939
|
if (!checked.ok) return { ok: false, refusals: checked.refusals, warnings: checked.warnings };
|
|
1883
|
-
const { steps, model: m } = setupSteps(economy, { now, only, live });
|
|
1940
|
+
const { steps, model: m } = setupSteps(economy, { now, only, live, renames });
|
|
1884
1941
|
if (ctx)
|
|
1885
1942
|
ctx.tokenPrograms ??= Object.fromEntries(
|
|
1886
1943
|
[...m.tokens.values()]
|