@xgamesfun/solana 0.24.0 → 0.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DISCLAIMER.md +1 -1
- package/NOTICE +4 -4
- package/README.md +100 -52
- package/bin/xsolana.mjs +32 -11
- package/examples/blind-forge.json +2 -4
- package/examples/coin-flip-arena.json +2 -5
- package/examples/duel-arena.json +1 -4
- package/examples/fair-presale.json +1 -3
- package/examples/hero-trials.json +14 -2
- package/examples/ideas.json +788 -788
- package/examples/nft-raffle.json +1 -0
- package/examples/partner-vault.json +19 -3
- package/examples/sacrifice-raffle.json +2 -10
- package/examples/shelter-fair.json +1 -0
- package/examples/stakers-draw.json +4 -7
- package/examples/trade-in-shop.json +2 -3
- package/js/approve/details.mjs +117 -48
- package/js/approve/ed25519.mjs +5 -1
- package/js/approve/live-check.mjs +83 -26
- package/js/approve/page.html +217 -78
- package/js/approve/page.js +43 -15
- package/js/approve/server.mjs +276 -74
- package/js/chain/base58.js +7 -1
- package/js/chain/borsh.js +15 -11
- package/js/chain/bytes.js +11 -8
- package/js/chain/core.js +21 -5
- package/js/chain/curve.js +1 -1
- package/js/chain/lookup.js +118 -36
- package/js/chain/mint.js +6 -2
- package/js/chain/pda.js +2 -2
- package/js/chain/programs.js +6 -6
- package/js/chain/rpc.js +40 -14
- package/js/chain/token.js +13 -2
- package/js/chain/token2022.js +55 -16
- package/js/chain/tx.js +32 -22
- package/js/client/accounts.js +270 -80
- package/js/client/compressed.js +4 -3
- package/js/client/grant.js +165 -40
- package/js/client/idl/loot.js +1 -1
- package/js/client/idl/loot.json +104 -16
- package/js/client/ix.js +2121 -457
- package/js/client/pda.js +20 -18
- package/js/client/policy.js +12 -5
- package/js/client/random.js +2 -2
- package/js/client/sizes.js +220 -65
- package/js/client/traits.js +14 -5
- package/js/connect/connect.mjs +59 -17
- package/js/economy/check.mjs +3884 -690
- package/js/economy/cost.mjs +381 -57
- package/js/economy/examples.mjs +176 -35
- package/js/economy/explain.mjs +468 -110
- package/js/economy/guide.mjs +282 -59
- package/js/economy/legal.mjs +12 -6
- package/js/economy/live.mjs +62 -20
- package/js/economy/model.mjs +320 -97
- package/js/economy/plan.mjs +1022 -203
- package/js/economy/review.mjs +760 -0
- package/js/economy/schema.json +134 -372
- package/js/economy/secrets.mjs +17 -7
- package/js/economy/simulate.mjs +771 -178
- package/js/economy/trust.mjs +15 -0
- package/js/economy/txsize.mjs +15 -3
- package/js/economy/validate.mjs +61 -24
- package/js/index.js +1 -0
- package/js/mcp/server.mjs +43 -15
- package/js/mcp/tools.mjs +318 -80
- package/js/play/index.js +1960 -461
- package/js/play/keys.js +35 -9
- package/js/results/index.js +225 -32
- package/js/results/server.js +230 -0
- package/js/results/wire.js +64 -16
- package/package.json +6 -4
- package/release.json +5 -5
- package/skills/xsolana-economy/SKILL.md +52 -22
- package/skills/xsolana-economy/references/after-launch.md +32 -21
- package/skills/xsolana-economy/references/apps.md +47 -36
- package/skills/xsolana-economy/references/sentence-to-spec.md +149 -129
- package/skills/xsolana-economy/references/telling-the-person.md +15 -6
- package/skills/xsolana-economy/references/trust-and-cheating.md +126 -0
- package/types/chain/core.d.ts +1 -1
- package/types/chain/lookup.d.ts +1 -1
- package/types/client/accounts.d.ts +24 -3
- package/types/client/grant.d.ts +2 -2
- package/types/client/idl/loot.d.ts +22 -0
- package/types/client/ix.d.ts +54 -45
- package/types/client/pda.d.ts +3 -1
- package/types/economy/cost.d.mts +1 -1
- package/types/economy/explain.d.mts +3 -0
- package/types/economy/guide.d.mts +5 -0
- package/types/economy/model.d.mts +48 -3
- package/types/economy/plan.d.mts +44 -2
- package/types/economy/review.d.mts +45 -0
- package/types/economy/trust.d.mts +8 -0
- package/types/index.d.ts +1 -0
- package/types/mcp/tools.d.mts +62 -0
- package/types/play/index.d.ts +14 -25
- package/types/results/index.d.ts +8 -7
- package/types/results/server.d.ts +126 -0
- package/types/results/wire.d.ts +1 -1
package/js/client/accounts.js
CHANGED
|
@@ -49,41 +49,109 @@ export const TAGS = Object.freeze({
|
|
|
49
49
|
const BY_TAG = Object.fromEntries(Object.entries(TAGS).map(([name, tag]) => [tag, name]));
|
|
50
50
|
|
|
51
51
|
/** The kind of a loot account from its first byte, or null. */
|
|
52
|
-
export const accountKind =
|
|
52
|
+
export const accountKind = data => (data?.length ? (BY_TAG[data[0]] ?? null) : null);
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
55
|
* The records the program writes by hand, as IDL types: a lottery round's header and what it keeps (`LotteryRound`,
|
|
56
56
|
* tag 16), and a contest's entry. The client reads them with the same Borsh codec as any account.
|
|
57
57
|
*/
|
|
58
58
|
const RECORDS = [
|
|
59
|
-
{
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
{
|
|
60
|
+
name: 'Round',
|
|
61
|
+
type: { kind: 'enum', variants: ['Open', 'Drawing', 'Drawn', 'Cancelled'].map(name => ({ name })) },
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'TokenPrize',
|
|
65
|
+
type: {
|
|
66
|
+
kind: 'struct',
|
|
67
|
+
fields: [
|
|
68
|
+
{ name: 'run', type: 'u16' },
|
|
69
|
+
{ name: 'mint', type: 'pubkey' },
|
|
70
|
+
{ name: 'each', type: 'u64' },
|
|
71
|
+
{ name: 'sent', type: 'u64' },
|
|
72
|
+
{ name: 'arrived', type: 'u64' },
|
|
73
|
+
{ name: 'minted', type: 'bool' },
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: 'Opened',
|
|
79
|
+
type: {
|
|
80
|
+
kind: 'struct',
|
|
81
|
+
fields: [
|
|
82
|
+
{ name: 'mint', type: 'pubkey' },
|
|
83
|
+
{ name: 'by', type: 'pubkey' },
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
},
|
|
62
87
|
{
|
|
63
88
|
name: 'LotteryRound',
|
|
64
89
|
type: {
|
|
65
90
|
kind: 'struct',
|
|
66
91
|
fields: [
|
|
67
|
-
['project', 'pubkey'],
|
|
92
|
+
['project', 'pubkey'],
|
|
93
|
+
['lottery', 'pubkey'],
|
|
94
|
+
['id', 'u32'],
|
|
95
|
+
['incarnation', 'u64'],
|
|
96
|
+
['round', 'u32'],
|
|
97
|
+
['bump', 'u8'],
|
|
98
|
+
['opener', 'pubkey'],
|
|
99
|
+
['lent', 'u64'],
|
|
100
|
+
['head', 'u32'],
|
|
68
101
|
['status', { defined: { name: 'Round' } }],
|
|
69
102
|
// The terms, as the lottery's revision had them at the round's first purchase; whether its entries keep what
|
|
70
103
|
// arrived (a payment mint with a transfer fee, or a share the buyer chooses, paid as they buy).
|
|
71
|
-
['revision', 'u32'],
|
|
104
|
+
['revision', 'u32'],
|
|
105
|
+
['terms', { defined: { name: 'LotteryTerms' } }],
|
|
106
|
+
['fee', 'bool'],
|
|
72
107
|
// The totals, split at each purchase: what each share paid in common takes, the entries that own a part of
|
|
73
108
|
// their own, and the parts owed now.
|
|
74
|
-
['sold', 'u64'],
|
|
109
|
+
['sold', 'u64'],
|
|
110
|
+
['to_pot', 'u64'],
|
|
111
|
+
['common', { vec: 'u64' }],
|
|
112
|
+
['owns', 'u32'],
|
|
113
|
+
['owed', 'u32'],
|
|
75
114
|
// The draw, and the money the ask fixed: the pot's (what the round owes the pot back: `pot_owed`).
|
|
76
|
-
['asked_first', 'u64'],
|
|
77
|
-
['
|
|
78
|
-
['
|
|
115
|
+
['asked_first', 'u64'],
|
|
116
|
+
['asked_slot', 'u64'],
|
|
117
|
+
['first_asked_at', 'i64'],
|
|
118
|
+
['asked_at', 'i64'],
|
|
119
|
+
['answer', { array: ['u8', 32] }],
|
|
120
|
+
['first', 'u64'],
|
|
121
|
+
['pot_total', 'u64'],
|
|
122
|
+
['pot_sent', 'u64'],
|
|
123
|
+
['pot_arrived', 'u64'],
|
|
124
|
+
['pot_owed', 'u64'],
|
|
125
|
+
['paid', 'bytes'],
|
|
126
|
+
['prizes', { vec: { defined: { name: 'TokenPrize' } } }],
|
|
127
|
+
['accounts', { vec: { defined: { name: 'Opened' } } }],
|
|
79
128
|
// The NFT of the lottery's own stash each place that holds or draws one got, in place order, and how many.
|
|
80
|
-
['nfts', { vec: 'pubkey' }],
|
|
129
|
+
['nfts', { vec: 'pubkey' }],
|
|
130
|
+
['assigned', 'u32'],
|
|
131
|
+
].map(([name, type]) => ({ name, type })),
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: 'ContestEntry',
|
|
136
|
+
type: {
|
|
137
|
+
kind: 'struct',
|
|
138
|
+
fields: [
|
|
139
|
+
['player', 'pubkey'],
|
|
140
|
+
['side', 'u16'],
|
|
141
|
+
['amount', 'u64'],
|
|
142
|
+
['bits', 'u8'],
|
|
143
|
+
['nft', 'pubkey'],
|
|
144
|
+
['place', 'u32'],
|
|
145
|
+
['taken', 'u32'],
|
|
81
146
|
].map(([name, type]) => ({ name, type })),
|
|
82
147
|
},
|
|
83
148
|
},
|
|
84
|
-
{ name: 'ContestEntry', type: { kind: 'struct', fields: [['player', 'pubkey'], ['side', 'u16'], ['amount', 'u64'], ['bits', 'u8'], ['nft', 'pubkey'], ['place', 'u32'], ['taken', 'u32']].map(([name, type]) => ({ name, type })) } },
|
|
85
149
|
];
|
|
86
|
-
const IDLX = {
|
|
150
|
+
const IDLX = {
|
|
151
|
+
...IDL,
|
|
152
|
+
types: [...IDL.types, ...RECORDS],
|
|
153
|
+
accounts: [...IDL.accounts, { name: 'LotteryRound', discriminator: [TAGS.LotteryRound] }],
|
|
154
|
+
};
|
|
87
155
|
|
|
88
156
|
/** A loot account of the named kind (one the IDL describes, or a `LotteryRound`). Throws when the data is not one. */
|
|
89
157
|
export const decode = (name, data) => decodeAccount(IDLX, name, data);
|
|
@@ -97,7 +165,8 @@ export function decodeAny(data) {
|
|
|
97
165
|
if (kind === 'Ends') return { kind, value: decodeEnds(data) };
|
|
98
166
|
if (kind === 'LotteryRound') return { kind, value: decodeRound(data) };
|
|
99
167
|
if (kind === 'Sheet') return { kind, value: decodeSheet(data) };
|
|
100
|
-
if (kind === 'Table')
|
|
168
|
+
if (kind === 'Table')
|
|
169
|
+
return data[37] === PAYLOAD.Nft ? { kind: 'Stash', value: decodeStash(data) } : { kind, value: decodeTable(data) };
|
|
101
170
|
if (kind === 'Proven') return { kind, value: decodeProven(data) };
|
|
102
171
|
if (kind === 'Traits') return { kind, value: decodeTraits(data) };
|
|
103
172
|
return { kind, value: decode(kind, data) };
|
|
@@ -124,28 +193,38 @@ export function decodeList(data) {
|
|
|
124
193
|
* it, the weight that stops earning then, and the seats that still need the record.
|
|
125
194
|
*/
|
|
126
195
|
export function decodeEnds(data) {
|
|
127
|
-
if (!(data instanceof Uint8Array) || data[0] !== TAGS.Ends || data.length < 7)
|
|
196
|
+
if (!(data instanceof Uint8Array) || data[0] !== TAGS.Ends || data.length < 7)
|
|
197
|
+
throw new Error('Not a record of ends');
|
|
128
198
|
const width = Number(readUintLE(data, 1, 2));
|
|
129
199
|
const count = Number(readUintLE(data, 3, 4));
|
|
130
|
-
if (width !== END_BYTES || data.length !== 7 + width * count)
|
|
200
|
+
if (width !== END_BYTES || data.length !== 7 + width * count)
|
|
201
|
+
throw new Error('The record of ends does not match its records');
|
|
131
202
|
return Array.from({ length: count }, (_, i) => {
|
|
132
203
|
const at = 7 + width * i;
|
|
133
204
|
let time = 0n;
|
|
134
205
|
for (let b = 0; b < 8; b++) time = (time << 8n) | BigInt(data[at + b]);
|
|
135
|
-
return {
|
|
206
|
+
return {
|
|
207
|
+
at: time,
|
|
208
|
+
rewardPerWeight: readUintLE(data, at + 8, 16),
|
|
209
|
+
weight: readUintLE(data, at + 24, 16),
|
|
210
|
+
seats: Number(readUintLE(data, at + 40, 4)),
|
|
211
|
+
};
|
|
136
212
|
});
|
|
137
213
|
}
|
|
138
214
|
|
|
139
215
|
/** The first 16 bytes of the sha256 of `parts` (addresses, bytes or u64s): a record's key in a list. */
|
|
140
|
-
export const recordKey16 = (...parts) =>
|
|
216
|
+
export const recordKey16 = (...parts) =>
|
|
217
|
+
sha256(
|
|
218
|
+
concat(...parts.map(p => (typeof p === 'string' ? addressBytes(p) : p instanceof Uint8Array ? p : uintLE(p, 8)))),
|
|
219
|
+
).subarray(0, 16);
|
|
141
220
|
|
|
142
221
|
/** The key of a wallet's count for a box, a recipe (incarnation 0) or a lottery (its incarnation), in its counts. */
|
|
143
222
|
export const countKey = (record, incarnation = 0) => recordKey16(record, BigInt(incarnation));
|
|
144
223
|
|
|
145
224
|
/** What the limit record at `key` counts in a decoded list (`decodeList`, or null for none): { count, window, inWindow }. */
|
|
146
225
|
export function readLimit(list, key) {
|
|
147
|
-
const record = list?.records.find(
|
|
148
|
-
const n =
|
|
226
|
+
const record = list?.records.find(r => compareBytes(r.subarray(0, 16), key) === 0);
|
|
227
|
+
const n = i => (record ? Number(readUintLE(record, 16 + 4 * i, 4)) : 0);
|
|
149
228
|
return { count: n(0), window: n(1), inWindow: n(2) };
|
|
150
229
|
}
|
|
151
230
|
|
|
@@ -153,7 +232,13 @@ export function readLimit(list, key) {
|
|
|
153
232
|
* as the list). Any other owner or layout is refused. */
|
|
154
233
|
export function readListAccount(account) {
|
|
155
234
|
if (account == null) return null;
|
|
156
|
-
if (
|
|
235
|
+
if (
|
|
236
|
+
account.owner === SYSTEM_PROGRAM &&
|
|
237
|
+
account.data instanceof Uint8Array &&
|
|
238
|
+
account.data.length === 0 &&
|
|
239
|
+
!account.executable
|
|
240
|
+
)
|
|
241
|
+
return null;
|
|
157
242
|
if (account.owner !== LOOT_PROGRAM || account.executable) throw new Error('The list has an invalid owner.');
|
|
158
243
|
return decodeList(account.data);
|
|
159
244
|
}
|
|
@@ -165,7 +250,7 @@ export function readListAccount(account) {
|
|
|
165
250
|
*/
|
|
166
251
|
export function decodeEntries(data) {
|
|
167
252
|
const { width, records } = decodeList(data);
|
|
168
|
-
return records.map(
|
|
253
|
+
return records.map(r => {
|
|
169
254
|
const full = new Uint8Array(83);
|
|
170
255
|
full.set(r.subarray(0, Math.min(width, 83)));
|
|
171
256
|
return decodeBorsh(IDLX, { defined: { name: 'ContestEntry' } }, full)[0];
|
|
@@ -178,14 +263,38 @@ export const ENDED = 2;
|
|
|
178
263
|
|
|
179
264
|
/** How many losers' NFTs a winner of a contest of NFTs takes: the share fixed at the decision, one more for an early
|
|
180
265
|
* place on its side. */
|
|
181
|
-
export const shareOf = (contest, entry) =>
|
|
266
|
+
export const shareOf = (contest, entry) =>
|
|
267
|
+
Number(contest.share) + (Number(entry.place) < Number(contest.extras) ? 1 : 0);
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* What a winning entry that put in `amount` takes with its first claim of a decided contest of tokens: its part of what
|
|
271
|
+
* the decision wrote (`pot_won`, the pot once the shares were taken; `prize_won`, a split prize's vault), by what it put
|
|
272
|
+
* in against its side's total. The winners claimed before it took `won × claimed ÷ total`, so the parts add up exactly
|
|
273
|
+
* whatever the order: `{ pot, prize }` (the prize part is 0 when the claim forgoes it). Tokens sent to the pot after the
|
|
274
|
+
* decision are nobody's.
|
|
275
|
+
*/
|
|
276
|
+
export function contestWinnings(contest, amount) {
|
|
277
|
+
const total = BigInt(contest.sides[contest.winner] ?? 0) || 1n;
|
|
278
|
+
const claimed = total - BigInt(contest.unclaimed);
|
|
279
|
+
const part = won => (BigInt(won) * (claimed + BigInt(amount))) / total - (BigInt(won) * claimed) / total;
|
|
280
|
+
return { pot: part(contest.pot_won), prize: part(contest.prize_won) };
|
|
281
|
+
}
|
|
182
282
|
|
|
183
283
|
/** What a count allows `n` more of at `now` under `limit` ({ total, per_window, window }, 0 is no limit; a lottery's
|
|
184
284
|
* window is its round plus one, given as `window`). */
|
|
185
|
-
export function countAllows(
|
|
285
|
+
export function countAllows(
|
|
286
|
+
count,
|
|
287
|
+
limit,
|
|
288
|
+
now,
|
|
289
|
+
n = 1,
|
|
290
|
+
window = Number(limit.window) ? Math.floor(Number(now) / Number(limit.window)) + 1 : 0,
|
|
291
|
+
) {
|
|
186
292
|
const total = (count?.count ?? 0) + n;
|
|
187
293
|
const inWindow = (count && count.window === window ? count.inWindow : 0) + n;
|
|
188
|
-
return (
|
|
294
|
+
return (
|
|
295
|
+
(!Number(limit.total) || total <= Number(limit.total)) &&
|
|
296
|
+
(!Number(limit.per_window) || inWindow <= Number(limit.per_window))
|
|
297
|
+
);
|
|
189
298
|
}
|
|
190
299
|
|
|
191
300
|
/**
|
|
@@ -194,7 +303,8 @@ export function countAllows(count, limit, now, n = 1, window = Number(limit.wind
|
|
|
194
303
|
*/
|
|
195
304
|
export function decodeSheet(data) {
|
|
196
305
|
if (data == null || data.length === 0) return null;
|
|
197
|
-
if (!(data instanceof Uint8Array) || data[0] !== TAGS.Sheet || data.length < 69)
|
|
306
|
+
if (!(data instanceof Uint8Array) || data[0] !== TAGS.Sheet || data.length < 69)
|
|
307
|
+
throw new Error('Not a Sheet account');
|
|
198
308
|
const { attributes, head } = attributesAt(data, 'Sheet');
|
|
199
309
|
return { project: head(1), nft: head(33), attributes };
|
|
200
310
|
}
|
|
@@ -203,7 +313,8 @@ export function decodeSheet(data) {
|
|
|
203
313
|
* the wallet (its XP, level, rank). Its layout is a Sheet's, under its own tag. */
|
|
204
314
|
export function decodeProfile(data) {
|
|
205
315
|
if (data == null || data.length === 0) return null;
|
|
206
|
-
if (!(data instanceof Uint8Array) || data[0] !== TAGS.Profile || data.length < 69)
|
|
316
|
+
if (!(data instanceof Uint8Array) || data[0] !== TAGS.Profile || data.length < 69)
|
|
317
|
+
throw new Error('Not a Profile account');
|
|
207
318
|
const { attributes, head } = attributesAt(data, 'Profile');
|
|
208
319
|
return { project: head(1), wallet: head(33), attributes };
|
|
209
320
|
}
|
|
@@ -224,7 +335,7 @@ function attributesAt(data, name) {
|
|
|
224
335
|
// Each attribute takes 8 bytes at least (two empty texts).
|
|
225
336
|
if (count * 8 > data.length - at) throw new Error(`${name} account is truncated`);
|
|
226
337
|
const attributes = Array.from({ length: count }, () => ({ key: text(), value: text() }));
|
|
227
|
-
return { attributes, end: at, head:
|
|
338
|
+
return { attributes, end: at, head: from => encodeBase58(data.subarray(from, from + 32)) };
|
|
228
339
|
}
|
|
229
340
|
|
|
230
341
|
/** What a table's rows carry (its payload byte). */
|
|
@@ -245,16 +356,25 @@ export function tableRowAt(len, r) {
|
|
|
245
356
|
*/
|
|
246
357
|
export function decodeTable(data) {
|
|
247
358
|
if (data == null || data.length === 0) return null;
|
|
248
|
-
if (!(data instanceof Uint8Array) || data[0] !== TAGS.Table || data.length < TABLE_HEAD)
|
|
249
|
-
|
|
359
|
+
if (!(data instanceof Uint8Array) || data[0] !== TAGS.Table || data.length < TABLE_HEAD)
|
|
360
|
+
throw new Error('Not a Table account');
|
|
361
|
+
const payload = Object.keys(PAYLOAD).find(k => PAYLOAD[k] === data[37]);
|
|
250
362
|
if (!payload || payload === 'Nft') throw new Error('Not a Table account');
|
|
251
363
|
const count = Number(readUintLE(data, 39, 4));
|
|
252
|
-
const head = {
|
|
364
|
+
const head = {
|
|
365
|
+
project: encodeBase58(data.subarray(1, 33)),
|
|
366
|
+
id: Number(readUintLE(data, 33, 4)),
|
|
367
|
+
payload,
|
|
368
|
+
sealed: data[38] === 1,
|
|
369
|
+
count,
|
|
370
|
+
total: readUintLE(data, 43, 8),
|
|
371
|
+
};
|
|
253
372
|
const len = payload === 'Label' ? 40 : 8;
|
|
254
373
|
if (count && tableRowAt(len, count - 1) + len > data.length) throw new Error('Table account is truncated');
|
|
255
374
|
const rows = Array.from({ length: count }, (_, r) => {
|
|
256
375
|
const at = tableRowAt(len, r);
|
|
257
|
-
const label =
|
|
376
|
+
const label =
|
|
377
|
+
payload === 'Label' ? new TextDecoder().decode(data.subarray(at + 8, at + 40)).replace(/\0+$/, '') : '';
|
|
258
378
|
return { weight: Number(readUintLE(data, at, 4)), left: Number(readUintLE(data, at + 4, 4)), label };
|
|
259
379
|
});
|
|
260
380
|
return { ...head, rows };
|
|
@@ -264,11 +384,17 @@ export function decodeTable(data) {
|
|
|
264
384
|
* give away and the weight each is drawn by, as a table of NFTs keeps them (a row: its weight, then the NFT). */
|
|
265
385
|
export function decodeStash(data) {
|
|
266
386
|
if (data == null || data.length === 0) return null;
|
|
267
|
-
if (!(data instanceof Uint8Array) || data[0] !== TAGS.Table || data[37] !== PAYLOAD.Nft || data.length < TABLE_HEAD)
|
|
387
|
+
if (!(data instanceof Uint8Array) || data[0] !== TAGS.Table || data[37] !== PAYLOAD.Nft || data.length < TABLE_HEAD)
|
|
388
|
+
throw new Error('Not a Stash account');
|
|
268
389
|
const count = Number(readUintLE(data, 39, 4));
|
|
269
390
|
if (count && tableRowAt(36, count - 1) + 36 > data.length) throw new Error('Stash account is truncated');
|
|
270
391
|
const rows = Array.from({ length: count }, (_, r) => tableRowAt(36, r));
|
|
271
|
-
return {
|
|
392
|
+
return {
|
|
393
|
+
project: encodeBase58(data.subarray(1, 33)),
|
|
394
|
+
id: Number(readUintLE(data, 33, 4)),
|
|
395
|
+
nfts: rows.map(at => encodeBase58(data.subarray(at + 4, at + 36))),
|
|
396
|
+
weights: rows.map(at => Number(readUintLE(data, at, 4))),
|
|
397
|
+
};
|
|
272
398
|
}
|
|
273
399
|
|
|
274
400
|
/**
|
|
@@ -279,9 +405,10 @@ export function decodeStash(data) {
|
|
|
279
405
|
export function decodeProven(data) {
|
|
280
406
|
// Its shortest: the tag, five addresses, the root, data hash and creator hash, no asset data hash, the flags and
|
|
281
407
|
// nonce, and its payer.
|
|
282
|
-
if (!(data instanceof Uint8Array) || data[0] !== TAGS.Proven || data.length < 1 + 32 * 8 + 1 + 1 + 8 + 32)
|
|
283
|
-
|
|
284
|
-
const
|
|
408
|
+
if (!(data instanceof Uint8Array) || data[0] !== TAGS.Proven || data.length < 1 + 32 * 8 + 1 + 1 + 8 + 32)
|
|
409
|
+
throw new Error('Not a Proven account');
|
|
410
|
+
const key = at => encodeBase58(data.subarray(at, at + 32));
|
|
411
|
+
const none = k => (k === NO_ADDRESS ? null : k);
|
|
285
412
|
const root = 1 + 32 * 5;
|
|
286
413
|
let at = root + 32 * 3; // the five addresses, then the root, data hash and creator hash
|
|
287
414
|
if (data[at] !== 0 && data[at] !== 1) throw new Error('Proven account has an unknown asset data hash flag');
|
|
@@ -290,15 +417,27 @@ export function decodeProven(data) {
|
|
|
290
417
|
if (at + 1 + 8 + 32 > data.length) throw new Error('Proven account is truncated');
|
|
291
418
|
const flags = data[at];
|
|
292
419
|
return {
|
|
293
|
-
asset: key(1),
|
|
294
|
-
|
|
420
|
+
asset: key(1),
|
|
421
|
+
tree: key(33),
|
|
422
|
+
owner: none(key(65)),
|
|
423
|
+
delegate: none(key(97)),
|
|
424
|
+
collection: none(key(129)),
|
|
425
|
+
flags,
|
|
426
|
+
nonce: readUintLE(data, at + 1, 8),
|
|
427
|
+
root: toHex(data.subarray(root, root + 32)),
|
|
428
|
+
payer: key(at + 9),
|
|
295
429
|
};
|
|
296
430
|
}
|
|
297
431
|
|
|
298
432
|
/** The traits a project vouches for (`["traits", project, key]`): { project, key, root (hex) }. */
|
|
299
433
|
export function decodeTraits(data) {
|
|
300
|
-
if (!(data instanceof Uint8Array) || data[0] !== TAGS.Traits || data.length < 97)
|
|
301
|
-
|
|
434
|
+
if (!(data instanceof Uint8Array) || data[0] !== TAGS.Traits || data.length < 97)
|
|
435
|
+
throw new Error('Not a Traits account');
|
|
436
|
+
return {
|
|
437
|
+
project: encodeBase58(data.subarray(1, 33)),
|
|
438
|
+
key: encodeBase58(data.subarray(33, 65)),
|
|
439
|
+
root: toHex(data.subarray(65, 97)),
|
|
440
|
+
};
|
|
302
441
|
}
|
|
303
442
|
|
|
304
443
|
/**
|
|
@@ -310,11 +449,11 @@ export function decodeTokenMetadata(data) {
|
|
|
310
449
|
if (!(data instanceof Uint8Array) || data[0] !== 4) return null;
|
|
311
450
|
let at = 33;
|
|
312
451
|
const key = () => encodeBase58(data.subarray(at, (at += 32)));
|
|
313
|
-
const skip =
|
|
452
|
+
const skip = n => (at += n);
|
|
314
453
|
const str = () => skip(4 + Number(readUintLE(data, at, 4)));
|
|
315
454
|
const some = () => at < data.length && data[at++] === 1;
|
|
316
455
|
const mint = key();
|
|
317
|
-
str(), str(), str(), skip(2);
|
|
456
|
+
(str(), str(), str(), skip(2));
|
|
318
457
|
if (some()) skip(34 * Number(readUintLE(data, at, 4)) + 4);
|
|
319
458
|
skip(2);
|
|
320
459
|
if (some()) skip(1); // edition nonce
|
|
@@ -346,13 +485,26 @@ export function decodeTokenMetadata(data) {
|
|
|
346
485
|
const END_BITS = (1n << 48n) - 1n;
|
|
347
486
|
export const OWN_PAID = 1n << 63n;
|
|
348
487
|
|
|
349
|
-
/** The mint a lottery's tickets are paid in (`terms`: a Lottery's or a round's): its ticket mint, burned, or
|
|
350
|
-
export const paymentMint =
|
|
488
|
+
/** The mint a lottery's tickets are paid in (`terms`: a Lottery's or a round's): its ticket mint, burned, or `mint`. */
|
|
489
|
+
export const paymentMint = terms => (ticketsBurn(terms) ? terms.ticket_mint : terms.mint);
|
|
351
490
|
/** Whether a lottery's tickets are burned: they are paid in a ticket mint of its own. */
|
|
352
|
-
export const ticketsBurn =
|
|
491
|
+
export const ticketsBurn = terms => Boolean(terms.ticket_mint) && terms.ticket_mint !== NO_ADDRESS;
|
|
492
|
+
|
|
493
|
+
/** The most pots one lottery keeps (`MAX_POTS`): a pot's id is below it. */
|
|
494
|
+
export const MAX_POTS = Number(IDL.constants.find(c => c.name === 'MAX_POTS').value);
|
|
495
|
+
/** The pot a lottery's places are paid from (`terms.pays_from`: `{ Pot: [id] }`), or null when each round pays from its
|
|
496
|
+
* own ticket money (`{ Round: { rest } }`). */
|
|
497
|
+
export const potIdOf = terms => (terms?.pays_from?.Pot ? Number(terms.pays_from.Pot[0]) : null);
|
|
498
|
+
/** Where what a round's places leave goes (`Part::Common` past the shares): its pot, or the `rest` a round paid from
|
|
499
|
+
* its own money names (`lotteryAddress`: the lottery's). */
|
|
500
|
+
export const leftoverTo = (lotteryAddress, terms) =>
|
|
501
|
+
potIdOf(terms) != null ? { Account: [pda.lotteryPot(lotteryAddress, potIdOf(terms))] } : terms.pays_from.Round.rest;
|
|
502
|
+
/** The ids of the pots a lottery opened (`Lottery::pots`, one bit an id), lowest first: a close empties each. */
|
|
503
|
+
export const lotteryPots = lottery =>
|
|
504
|
+
Array.from({ length: MAX_POTS }, (_, id) => id).filter(id => (Number(lottery.pots ?? 0) >> id) & 1);
|
|
353
505
|
|
|
354
506
|
/** A share the buyer chooses (a referrer, a collectible's holder): paid out of the price as they buy, from their own account. */
|
|
355
|
-
export const chosenAtPurchase =
|
|
507
|
+
export const chosenAtPurchase = share => Boolean(share.to?.Chosen);
|
|
356
508
|
|
|
357
509
|
/** The bytes of one entry of a round: buyer, word, what arrived (`fee`: a fee mint, or a chosen share), the NFT ticket. */
|
|
358
510
|
export const entryWidth = (fee, nft) => 40 + (fee ? 8 : 0) + (nft ? 32 : 0);
|
|
@@ -368,16 +520,24 @@ export function decodeRound(data) {
|
|
|
368
520
|
const at = Number(r.head);
|
|
369
521
|
const width = Number(readUintLE(data, at, 2));
|
|
370
522
|
const count = Number(readUintLE(data, at + 2, 4));
|
|
371
|
-
if (width !== entryWidth(r.fee, r.terms.nft_ticket != null) || data.length !== at + 6 + width * count)
|
|
523
|
+
if (width !== entryWidth(r.fee, r.terms.nft_ticket != null) || data.length !== at + 6 + width * count)
|
|
524
|
+
throw new Error('LotteryRound entries do not match its header');
|
|
372
525
|
let start = 0n;
|
|
373
526
|
const entries = Array.from({ length: count }, (_, i) => {
|
|
374
527
|
const e = data.subarray(at + 6 + width * i, at + 6 + width * (i + 1));
|
|
375
528
|
const word = readUintLE(e, 32, 8);
|
|
376
529
|
const end = word & END_BITS;
|
|
377
530
|
let k = 40;
|
|
378
|
-
const take =
|
|
531
|
+
const take = n => e.subarray((k += n) - n, k);
|
|
379
532
|
const amount = r.fee ? readUintLE(take(8), 0, 8) : (end - start) * BigInt(r.terms.price);
|
|
380
|
-
const entry = {
|
|
533
|
+
const entry = {
|
|
534
|
+
buyer: encodeBase58(e.subarray(0, 32)),
|
|
535
|
+
start,
|
|
536
|
+
end,
|
|
537
|
+
paid: word & ~END_BITS,
|
|
538
|
+
amount,
|
|
539
|
+
nft: r.terms.nft_ticket ? encodeBase58(take(32)) : NO_ADDRESS,
|
|
540
|
+
};
|
|
381
541
|
start = end;
|
|
382
542
|
return entry;
|
|
383
543
|
});
|
|
@@ -385,10 +545,10 @@ export function decodeRound(data) {
|
|
|
385
545
|
}
|
|
386
546
|
|
|
387
547
|
/** How many places a lottery's (or a round's) `places` are, every run counted (`Place.times`). */
|
|
388
|
-
export const placeCount =
|
|
548
|
+
export const placeCount = places => places.reduce((n, p) => n + Number(p.times ?? 1), 0);
|
|
389
549
|
|
|
390
550
|
/** The places of a round that have a winner: every place, but no more than tickets. */
|
|
391
|
-
export const roundWinners =
|
|
551
|
+
export const roundWinners = r => Math.min(placeCount(r.terms.places), Number(r.sold));
|
|
392
552
|
|
|
393
553
|
/** The ticket that wins `place` of a drawn round: even steps around the list from first place's ticket. */
|
|
394
554
|
export function ticketOfPlace(r, place) {
|
|
@@ -424,7 +584,10 @@ export function placesIn(r, run, start, end) {
|
|
|
424
584
|
const step = sold / BigInt(roundWinners(r));
|
|
425
585
|
const d = (BigInt(start) + sold - BigInt(r.first)) % sold;
|
|
426
586
|
const len = BigInt(end) - BigInt(start);
|
|
427
|
-
for (const [a, b] of [
|
|
587
|
+
for (const [a, b] of [
|
|
588
|
+
[d, d + len < sold ? d + len : sold],
|
|
589
|
+
[0n, d + len > sold ? d + len - sold : 0n],
|
|
590
|
+
]) {
|
|
428
591
|
let p = Number((a + step - 1n) / step);
|
|
429
592
|
for (p = Math.max(p, from); p < to && BigInt(p) * step < b; p++) out.push(p);
|
|
430
593
|
}
|
|
@@ -478,13 +641,17 @@ export const placePaid = (r, place, prize) => {
|
|
|
478
641
|
* what it takes: [{ place, amount }]. A pull pays all of them. */
|
|
479
642
|
function unpaidPlaces(r, i, run, prize) {
|
|
480
643
|
const e = r.entries[i];
|
|
481
|
-
const k = prize ? r.prizes.findIndex(
|
|
644
|
+
const k = prize ? r.prizes.findIndex(p => Number(p.run) === run) : -1;
|
|
482
645
|
if (prize && k < 0) return [];
|
|
483
|
-
const runs =
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
646
|
+
const runs =
|
|
647
|
+
k >= 0
|
|
648
|
+
? [[BigInt(r.prizes[k].each), runPlaces(r, run)[1] - runPlaces(r, run)[0]]]
|
|
649
|
+
: r.terms.places.map((p, j) => [bpsOf(r.pot_total, p.bps), runPlaces(r, j)[1] - runPlaces(r, j)[0]]);
|
|
650
|
+
const [sent, arrived, from] =
|
|
651
|
+
k >= 0 ? [r.prizes[k].sent, r.prizes[k].arrived, runPlaces(r, run)[0]] : [r.pot_sent, r.pot_arrived, 0];
|
|
652
|
+
return placesIn(r, run, e.start, e.end)
|
|
653
|
+
.filter(p => !placePaid(r, p, prize))
|
|
654
|
+
.map(place => ({ place, amount: splitPart(runs, sent, arrived, place - from) }));
|
|
488
655
|
}
|
|
489
656
|
|
|
490
657
|
/** What entry `i`'s places in run `run` take and are not paid yet, of its pot part or (`prize`) its token prize. */
|
|
@@ -492,18 +659,19 @@ export const placesOwed = (r, i, run, prize) => unpaidPlaces(r, i, run, prize).r
|
|
|
492
659
|
|
|
493
660
|
/** How many of the round's owed parts a pull of `{ Pot: [run] }` or a token `{ Prize: [run] }` for entry `i` settles:
|
|
494
661
|
* its places there that take more than nothing (one pull pays them all). */
|
|
495
|
-
export const placesPulled = (r, i, run, prize) => unpaidPlaces(r, i, run, prize).filter(
|
|
662
|
+
export const placesPulled = (r, i, run, prize) => unpaidPlaces(r, i, run, prize).filter(p => p.amount > 0n).length;
|
|
496
663
|
|
|
497
664
|
/** Whether run `run` of a round's places gives tokens (its prize is fixed at the ask, a `TokenPrize`). */
|
|
498
665
|
export const tokenRun = (r, run) => Boolean(r.terms.places[run]?.prize?.Tokens);
|
|
499
666
|
|
|
500
667
|
/** A place whose prize is an NFT of the lottery's own stash (`Held`, `Raffled`): assigned by the pulls, in place order. */
|
|
501
|
-
export const ownStash =
|
|
668
|
+
export const ownStash = place =>
|
|
669
|
+
Boolean(place.prize?.Held) || place.prize === 'Raffled' || place.prize?.Raffled !== undefined;
|
|
502
670
|
|
|
503
671
|
/** Entry `i`'s places in run `run` whose prize (not tokens) is not given yet, lowest first. */
|
|
504
672
|
export const prizePlacesOwed = (r, i, run) => {
|
|
505
673
|
const e = r.entries[i];
|
|
506
|
-
return placesIn(r, run, e.start, e.end).filter(
|
|
674
|
+
return placesIn(r, run, e.start, e.end).filter(p => !placePaid(r, p, true));
|
|
507
675
|
};
|
|
508
676
|
|
|
509
677
|
/** Where in `r.nfts` the NFT of `place` is (a place of a run that holds or draws one of the lottery's own stash). */
|
|
@@ -533,7 +701,7 @@ export function stashPlacesWon(r) {
|
|
|
533
701
|
export function awaitsNfts(r, i, run) {
|
|
534
702
|
if (!ownStash(r.terms.places[run])) return false;
|
|
535
703
|
const places = prizePlacesOwed(r, i, run);
|
|
536
|
-
return places.some(
|
|
704
|
+
return places.some(p => nftSlot(r, p) >= Number(r.assigned));
|
|
537
705
|
}
|
|
538
706
|
|
|
539
707
|
/**
|
|
@@ -553,44 +721,54 @@ export function partsOwed(r, i, { places = true } = {}) {
|
|
|
553
721
|
for (let run = 0; run < r.terms.places.length; run++) {
|
|
554
722
|
const prize = r.terms.places[run].prize;
|
|
555
723
|
if (placesOwed(r, i, run, false) > 0n) parts.push({ Pot: [run] });
|
|
556
|
-
if (tokenRun(r, run) ? placesOwed(r, i, run, true) > 0n : prize != null && prizePlacesOwed(r, i, run).length > 0)
|
|
724
|
+
if (tokenRun(r, run) ? placesOwed(r, i, run, true) > 0n : prize != null && prizePlacesOwed(r, i, run).length > 0)
|
|
725
|
+
parts.push({ Prize: [run] });
|
|
557
726
|
}
|
|
558
727
|
return parts;
|
|
559
728
|
}
|
|
560
729
|
|
|
561
730
|
/**
|
|
562
731
|
* The parts of a round owed in common, of no entry (`{ Common: [i] }`): each share paid in common once drawn (one that
|
|
563
|
-
* burns is burned at the close), then what
|
|
732
|
+
* burns is burned at the close), then what its places leave, owed to its pot or to `rest` (`i` = the shares' count), then after a
|
|
564
733
|
* cancel each pool's token prize back to its pool (past it).
|
|
565
734
|
*/
|
|
566
735
|
export function commonOwed(r) {
|
|
567
736
|
const n = r.terms.shares.length;
|
|
568
737
|
const out = [];
|
|
569
|
-
if (r.status === 'Drawn')
|
|
570
|
-
|
|
571
|
-
|
|
738
|
+
if (r.status === 'Drawn')
|
|
739
|
+
r.terms.shares.forEach((s, i) => s.to !== 'Burn' && BigInt(r.common[i] ?? 0) > 0n && out.push({ Common: [i] }));
|
|
740
|
+
// What the places leave, to the pot or to `rest`: a burned rest stays for the close to burn, and is no part owed.
|
|
741
|
+
if (
|
|
742
|
+
(r.status === 'Drawn' || r.status === 'Cancelled') &&
|
|
743
|
+
BigInt(r.pot_owed) > 0n &&
|
|
744
|
+
leftoverTo(r.lottery, r.terms) !== 'Burn'
|
|
745
|
+
)
|
|
746
|
+
out.push({ Common: [n] });
|
|
747
|
+
if (r.status === 'Cancelled')
|
|
748
|
+
r.prizes.forEach((p, k) => !p.minted && BigInt(p.arrived) > 0n && out.push({ Common: [n + 1 + k] }));
|
|
572
749
|
return out;
|
|
573
750
|
}
|
|
574
751
|
|
|
575
752
|
/** The entry of a round holding `ticket`. */
|
|
576
|
-
export const entryOf = (r, ticket) => r.entries.findIndex(
|
|
753
|
+
export const entryOf = (r, ticket) => r.entries.findIndex(e => e.start <= BigInt(ticket) && BigInt(ticket) < e.end);
|
|
577
754
|
|
|
578
755
|
/** Whether a round is done and owes nothing: the next pull closes it, given its close's accounts. */
|
|
579
|
-
export const roundFinished =
|
|
756
|
+
export const roundFinished = r => (r.status === 'Drawn' || r.status === 'Cancelled') && Number(r.owed) === 0;
|
|
580
757
|
|
|
581
|
-
/** Whether a lottery's place wins something: a
|
|
582
|
-
export const placeSet =
|
|
758
|
+
/** Whether a lottery's place wins something: a part of what the round pays from, a prize, or both. */
|
|
759
|
+
export const placeSet = place => Number(place.bps) > 0 || place.prize != null;
|
|
583
760
|
|
|
584
761
|
/** The address of a lottery's current round's record (there or not). */
|
|
585
762
|
export const roundAddress = (lotteryAddress, lottery) => pda.round(lotteryAddress, lottery.incarnation, lottery.round);
|
|
586
763
|
|
|
587
764
|
/** Whether a lottery (decoded) drew once and has ended. */
|
|
588
|
-
export const lotteryEnded =
|
|
765
|
+
export const lotteryEnded = lottery => Number(lottery.every) === 0 && Number(lottery.round) > 0;
|
|
589
766
|
|
|
590
767
|
/** The round a lottery's close, its write again and its own stash's moves name (`lottery::check_idle`): its current
|
|
591
768
|
* round's, or once a lottery that draws once has ended, round 0's (which keeps its stash's NFTs until its places took
|
|
592
769
|
* them out). */
|
|
593
|
-
export const idleRound = (lotteryAddress, lottery) =>
|
|
770
|
+
export const idleRound = (lotteryAddress, lottery) =>
|
|
771
|
+
pda.round(lotteryAddress, lottery.incarnation, lotteryEnded(lottery) ? 0 : lottery.round);
|
|
594
772
|
|
|
595
773
|
/**
|
|
596
774
|
* Who rents a listed NFT at `now` (Unix seconds), from its decoded Listing (or null for none): the renter's wallet while
|
|
@@ -602,7 +780,7 @@ export function renterOf(listing, now) {
|
|
|
602
780
|
}
|
|
603
781
|
|
|
604
782
|
/** Whether a schedule pays every run due when it is sent late (`schedule::catches_up`): only a sealed fixed amount. */
|
|
605
|
-
export const scheduleCatchesUp =
|
|
783
|
+
export const scheduleCatchesUp = schedule => Boolean(schedule.action?.from?.Vest && schedule.action?.amount?.Fixed);
|
|
606
784
|
|
|
607
785
|
/**
|
|
608
786
|
* How many runs of a schedule are due at `now` (Unix seconds, the chain's clock, never the computer's). One `run_schedule`
|
|
@@ -612,7 +790,8 @@ export const scheduleCatchesUp = (schedule) => Boolean(schedule.action?.from?.Ve
|
|
|
612
790
|
* (`vaultBalance`), which goes where it always went.
|
|
613
791
|
*/
|
|
614
792
|
export function runsDue(schedule, now, vaultBalance = 0n) {
|
|
615
|
-
const runs = Number(schedule.runs),
|
|
793
|
+
const runs = Number(schedule.runs),
|
|
794
|
+
max = Number(schedule.max_runs);
|
|
616
795
|
if (max > 0 && runs >= max) return schedule.action?.from?.Vest && BigInt(vaultBalance) > 0n ? 1 : 0;
|
|
617
796
|
if (!schedule.active || BigInt(now) < BigInt(schedule.next_at)) return 0;
|
|
618
797
|
const due = Number((BigInt(now) - BigInt(schedule.next_at)) / BigInt(schedule.every)) + 1;
|
|
@@ -630,16 +809,22 @@ export function runsDue(schedule, now, vaultBalance = 0n) {
|
|
|
630
809
|
*/
|
|
631
810
|
export function receipts(tx, err) {
|
|
632
811
|
if (Array.isArray(tx)) {
|
|
633
|
-
if (err === undefined)
|
|
812
|
+
if (err === undefined)
|
|
813
|
+
throw new Error(
|
|
814
|
+
"loot.receipts(logs, err) needs the transaction's meta.err (null when it succeeded): a failed transaction keeps its logs",
|
|
815
|
+
);
|
|
634
816
|
return err === null ? decodeEvents(IDL, tx) : [];
|
|
635
817
|
}
|
|
636
818
|
const meta = tx?.meta;
|
|
637
|
-
if (!meta || meta.err === undefined)
|
|
819
|
+
if (!meta || meta.err === undefined)
|
|
820
|
+
throw new Error(
|
|
821
|
+
'loot.receipts(tx) needs the transaction getTransaction returns, with its meta.err and meta.logMessages',
|
|
822
|
+
);
|
|
638
823
|
return meta.err === null ? decodeEvents(IDL, meta.logMessages ?? []) : [];
|
|
639
824
|
}
|
|
640
825
|
|
|
641
826
|
/** The program's errors by code: { code: { name, msg } }. */
|
|
642
|
-
export const ERRORS = Object.freeze(Object.fromEntries(IDL.errors.map(
|
|
827
|
+
export const ERRORS = Object.freeze(Object.fromEntries(IDL.errors.map(e => [e.code, { name: e.name, msg: e.msg }])));
|
|
643
828
|
|
|
644
829
|
/**
|
|
645
830
|
* The loot error in a failed transaction's logs ("custom program error: 0x…", or Anchor's "Error Code: …" line), or
|
|
@@ -671,7 +856,12 @@ export function lootError(logs) {
|
|
|
671
856
|
// A line that names no program (a log line, or an error's own text) is the running program's, when it is known.
|
|
672
857
|
if (running.length && running.at(-1) !== LOOT_PROGRAM) continue;
|
|
673
858
|
const named = /Error Code: (\w+)\. Error Number: (\d+)\. Error Message: (.+?)\.?$/.exec(line);
|
|
674
|
-
if (named)
|
|
859
|
+
if (named)
|
|
860
|
+
return {
|
|
861
|
+
code: Number(named[2]),
|
|
862
|
+
name: named[1],
|
|
863
|
+
msg: ERRORS[Number(named[2])]?.name === named[1] ? ERRORS[Number(named[2])].msg : named[3],
|
|
864
|
+
};
|
|
675
865
|
const m = /custom program error: 0x([0-9a-f]+)/i.exec(line);
|
|
676
866
|
if (m && ERRORS[parseInt(m[1], 16)]) return { code: parseInt(m[1], 16), ...ERRORS[parseInt(m[1], 16)] };
|
|
677
867
|
}
|