@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.
Files changed (99) hide show
  1. package/DISCLAIMER.md +1 -1
  2. package/NOTICE +4 -4
  3. package/README.md +100 -52
  4. package/bin/xsolana.mjs +32 -11
  5. package/examples/blind-forge.json +2 -4
  6. package/examples/coin-flip-arena.json +2 -5
  7. package/examples/duel-arena.json +1 -4
  8. package/examples/fair-presale.json +1 -3
  9. package/examples/hero-trials.json +14 -2
  10. package/examples/ideas.json +788 -788
  11. package/examples/nft-raffle.json +1 -0
  12. package/examples/partner-vault.json +19 -3
  13. package/examples/sacrifice-raffle.json +2 -10
  14. package/examples/shelter-fair.json +1 -0
  15. package/examples/stakers-draw.json +4 -7
  16. package/examples/trade-in-shop.json +2 -3
  17. package/js/approve/details.mjs +117 -48
  18. package/js/approve/ed25519.mjs +5 -1
  19. package/js/approve/live-check.mjs +83 -26
  20. package/js/approve/page.html +217 -78
  21. package/js/approve/page.js +43 -15
  22. package/js/approve/server.mjs +276 -74
  23. package/js/chain/base58.js +7 -1
  24. package/js/chain/borsh.js +15 -11
  25. package/js/chain/bytes.js +11 -8
  26. package/js/chain/core.js +21 -5
  27. package/js/chain/curve.js +1 -1
  28. package/js/chain/lookup.js +118 -36
  29. package/js/chain/mint.js +6 -2
  30. package/js/chain/pda.js +2 -2
  31. package/js/chain/programs.js +6 -6
  32. package/js/chain/rpc.js +40 -14
  33. package/js/chain/token.js +13 -2
  34. package/js/chain/token2022.js +55 -16
  35. package/js/chain/tx.js +32 -22
  36. package/js/client/accounts.js +270 -80
  37. package/js/client/compressed.js +4 -3
  38. package/js/client/grant.js +165 -40
  39. package/js/client/idl/loot.js +1 -1
  40. package/js/client/idl/loot.json +104 -16
  41. package/js/client/ix.js +2121 -457
  42. package/js/client/pda.js +20 -18
  43. package/js/client/policy.js +12 -5
  44. package/js/client/random.js +2 -2
  45. package/js/client/sizes.js +220 -65
  46. package/js/client/traits.js +14 -5
  47. package/js/connect/connect.mjs +59 -17
  48. package/js/economy/check.mjs +3884 -690
  49. package/js/economy/cost.mjs +381 -57
  50. package/js/economy/examples.mjs +176 -35
  51. package/js/economy/explain.mjs +468 -110
  52. package/js/economy/guide.mjs +282 -59
  53. package/js/economy/legal.mjs +12 -6
  54. package/js/economy/live.mjs +62 -20
  55. package/js/economy/model.mjs +320 -97
  56. package/js/economy/plan.mjs +1022 -203
  57. package/js/economy/review.mjs +760 -0
  58. package/js/economy/schema.json +134 -372
  59. package/js/economy/secrets.mjs +17 -7
  60. package/js/economy/simulate.mjs +771 -178
  61. package/js/economy/trust.mjs +15 -0
  62. package/js/economy/txsize.mjs +15 -3
  63. package/js/economy/validate.mjs +61 -24
  64. package/js/index.js +1 -0
  65. package/js/mcp/server.mjs +43 -15
  66. package/js/mcp/tools.mjs +318 -80
  67. package/js/play/index.js +1960 -461
  68. package/js/play/keys.js +35 -9
  69. package/js/results/index.js +225 -32
  70. package/js/results/server.js +230 -0
  71. package/js/results/wire.js +64 -16
  72. package/package.json +6 -4
  73. package/release.json +5 -5
  74. package/skills/xsolana-economy/SKILL.md +52 -22
  75. package/skills/xsolana-economy/references/after-launch.md +32 -21
  76. package/skills/xsolana-economy/references/apps.md +47 -36
  77. package/skills/xsolana-economy/references/sentence-to-spec.md +149 -129
  78. package/skills/xsolana-economy/references/telling-the-person.md +15 -6
  79. package/skills/xsolana-economy/references/trust-and-cheating.md +126 -0
  80. package/types/chain/core.d.ts +1 -1
  81. package/types/chain/lookup.d.ts +1 -1
  82. package/types/client/accounts.d.ts +24 -3
  83. package/types/client/grant.d.ts +2 -2
  84. package/types/client/idl/loot.d.ts +22 -0
  85. package/types/client/ix.d.ts +54 -45
  86. package/types/client/pda.d.ts +3 -1
  87. package/types/economy/cost.d.mts +1 -1
  88. package/types/economy/explain.d.mts +3 -0
  89. package/types/economy/guide.d.mts +5 -0
  90. package/types/economy/model.d.mts +48 -3
  91. package/types/economy/plan.d.mts +44 -2
  92. package/types/economy/review.d.mts +45 -0
  93. package/types/economy/trust.d.mts +8 -0
  94. package/types/index.d.ts +1 -0
  95. package/types/mcp/tools.d.mts +62 -0
  96. package/types/play/index.d.ts +14 -25
  97. package/types/results/index.d.ts +8 -7
  98. package/types/results/server.d.ts +126 -0
  99. package/types/results/wire.d.ts +1 -1
@@ -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 = (data) => (data?.length ? BY_TAG[data[0]] ?? null : null);
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
- { name: 'Round', type: { kind: 'enum', variants: ['Open', 'Drawing', 'Drawn', 'Cancelled'].map((name) => ({ name })) } },
60
- { name: 'TokenPrize', type: { kind: 'struct', fields: [{ name: 'run', type: 'u16' }, { name: 'mint', type: 'pubkey' }, { name: 'each', type: 'u64' }, { name: 'sent', type: 'u64' }, { name: 'arrived', type: 'u64' }, { name: 'minted', type: 'bool' }] } },
61
- { name: 'Opened', type: { kind: 'struct', fields: [{ name: 'mint', type: 'pubkey' }, { name: 'by', type: 'pubkey' }] } },
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'], ['lottery', 'pubkey'], ['id', 'u32'], ['incarnation', 'u64'], ['round', 'u32'], ['bump', 'u8'], ['opener', 'pubkey'], ['lent', 'u64'], ['head', 'u32'],
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'], ['terms', { defined: { name: 'LotteryTerms' } }], ['fee', 'bool'],
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'], ['to_pot', 'u64'], ['common', { vec: 'u64' }], ['owns', 'u32'], ['owed', 'u32'],
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'], ['asked_slot', 'u64'], ['first_asked_at', 'i64'], ['asked_at', 'i64'], ['answer', { array: ['u8', 32] }], ['first', 'u64'],
77
- ['pot_total', 'u64'], ['pot_sent', 'u64'], ['pot_arrived', 'u64'], ['pot_owed', 'u64'], ['paid', 'bytes'],
78
- ['prizes', { vec: { defined: { name: 'TokenPrize' } } }], ['accounts', { vec: { defined: { name: 'Opened' } } }],
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' }], ['assigned', 'u32'],
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 = { ...IDL, types: [...IDL.types, ...RECORDS], accounts: [...IDL.accounts, { name: 'LotteryRound', discriminator: [TAGS.LotteryRound] }] };
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') return data[37] === PAYLOAD.Nft ? { kind: 'Stash', value: decodeStash(data) } : { kind, value: decodeTable(data) };
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) throw new Error('Not a record of ends');
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) throw new Error('The record of ends does not match its records');
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 { at: time, rewardPerWeight: readUintLE(data, at + 8, 16), weight: readUintLE(data, at + 24, 16), seats: Number(readUintLE(data, at + 40, 4)) };
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) => sha256(concat(...parts.map((p) => (typeof p === 'string' ? addressBytes(p) : p instanceof Uint8Array ? p : uintLE(p, 8))))).subarray(0, 16);
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((r) => compareBytes(r.subarray(0, 16), key) === 0);
148
- const n = (i) => (record ? Number(readUintLE(record, 16 + 4 * i, 4)) : 0);
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 (account.owner === SYSTEM_PROGRAM && account.data instanceof Uint8Array && account.data.length === 0 && !account.executable) return null;
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((r) => {
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) => Number(contest.share) + (Number(entry.place) < Number(contest.extras) ? 1 : 0);
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(count, limit, now, n = 1, window = Number(limit.window) ? Math.floor(Number(now) / Number(limit.window)) + 1 : 0) {
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 (!Number(limit.total) || total <= Number(limit.total)) && (!Number(limit.per_window) || inWindow <= Number(limit.per_window));
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) throw new Error('Not a Sheet account');
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) throw new Error('Not a Profile account');
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: (from) => encodeBase58(data.subarray(from, from + 32)) };
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) throw new Error('Not a Table account');
249
- const payload = Object.keys(PAYLOAD).find((k) => PAYLOAD[k] === data[37]);
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 = { project: encodeBase58(data.subarray(1, 33)), id: Number(readUintLE(data, 33, 4)), payload, sealed: data[38] === 1, count, total: readUintLE(data, 43, 8) };
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 = payload === 'Label' ? new TextDecoder().decode(data.subarray(at + 8, at + 40)).replace(/\0+$/, '') : '';
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) throw new Error('Not a Stash account');
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 { project: encodeBase58(data.subarray(1, 33)), id: Number(readUintLE(data, 33, 4)), nfts: rows.map((at) => encodeBase58(data.subarray(at + 4, at + 36))), weights: rows.map((at) => Number(readUintLE(data, at, 4))) };
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) throw new Error('Not a Proven account');
283
- const key = (at) => encodeBase58(data.subarray(at, at + 32));
284
- const none = (k) => (k === NO_ADDRESS ? null : k);
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), tree: key(33), owner: none(key(65)), delegate: none(key(97)), collection: none(key(129)),
294
- flags, nonce: readUintLE(data, at + 1, 8), root: toHex(data.subarray(root, root + 32)), payer: key(at + 9),
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) throw new Error('Not a Traits account');
301
- return { project: encodeBase58(data.subarray(1, 33)), key: encodeBase58(data.subarray(33, 65)), root: toHex(data.subarray(65, 97)) };
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 = (n) => (at += n);
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 its pot's. */
350
- export const paymentMint = (terms) => (ticketsBurn(terms) ? terms.ticket_mint : terms.mint);
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 = (terms) => Boolean(terms.ticket_mint) && terms.ticket_mint !== NO_ADDRESS;
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 = (share) => Boolean(share.to?.Chosen);
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) throw new Error('LotteryRound entries do not match its header');
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 = (n) => e.subarray((k += n) - n, k);
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 = { buyer: encodeBase58(e.subarray(0, 32)), start, end, paid: word & ~END_BITS, amount, nft: r.terms.nft_ticket ? encodeBase58(take(32)) : NO_ADDRESS };
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 = (places) => places.reduce((n, p) => n + Number(p.times ?? 1), 0);
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 = (r) => Math.min(placeCount(r.terms.places), Number(r.sold));
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 [[d, d + len < sold ? d + len : sold], [0n, d + len > sold ? d + len - sold : 0n]]) {
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((p) => Number(p.run) === run) : -1;
644
+ const k = prize ? r.prizes.findIndex(p => Number(p.run) === run) : -1;
482
645
  if (prize && k < 0) return [];
483
- const runs = k >= 0
484
- ? [[BigInt(r.prizes[k].each), runPlaces(r, run)[1] - runPlaces(r, run)[0]]]
485
- : r.terms.places.map((p, j) => [bpsOf(r.pot_total, p.bps), runPlaces(r, j)[1] - runPlaces(r, j)[0]]);
486
- const [sent, arrived, from] = k >= 0 ? [r.prizes[k].sent, r.prizes[k].arrived, runPlaces(r, run)[0]] : [r.pot_sent, r.pot_arrived, 0];
487
- return placesIn(r, run, e.start, e.end).filter((p) => !placePaid(r, p, prize)).map((place) => ({ place, amount: splitPart(runs, sent, arrived, place - from) }));
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((p) => p.amount > 0n).length;
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 = (place) => Boolean(place.prize?.Held) || place.prize === 'Raffled' || place.prize?.Raffled !== undefined;
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((p) => !placePaid(r, p, true));
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((p) => nftSlot(r, p) >= Number(r.assigned));
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) parts.push({ Prize: [run] });
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 the round owes the lottery's pot (`i` = the shares' count), then after a
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') r.terms.shares.forEach((s, i) => s.to !== 'Burn' && BigInt(r.common[i] ?? 0) > 0n && out.push({ Common: [i] }));
570
- if ((r.status === 'Drawn' || r.status === 'Cancelled') && BigInt(r.pot_owed) > 0n) out.push({ Common: [n] });
571
- if (r.status === 'Cancelled') r.prizes.forEach((p, k) => !p.minted && BigInt(p.arrived) > 0n && out.push({ Common: [n + 1 + k] }));
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((e) => e.start <= BigInt(ticket) && BigInt(ticket) < e.end);
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 = (r) => (r.status === 'Drawn' || r.status === 'Cancelled') && Number(r.owed) === 0;
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 share of the pot, a prize, or both. */
582
- export const placeSet = (place) => Number(place.bps) > 0 || place.prize != null;
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 = (lottery) => Number(lottery.every) === 0 && Number(lottery.round) > 0;
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) => pda.round(lotteryAddress, lottery.incarnation, lotteryEnded(lottery) ? 0 : lottery.round);
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 = (schedule) => Boolean(schedule.action?.from?.Vest && schedule.action?.amount?.Fixed);
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), max = Number(schedule.max_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) throw new Error("loot.receipts(logs, err) needs the transaction's meta.err (null when it succeeded): a failed transaction keeps its logs");
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) throw new Error('loot.receipts(tx) needs the transaction getTransaction returns, with its meta.err and meta.logMessages');
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((e) => [e.code, { name: e.name, msg: e.msg }])));
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) return { code: Number(named[2]), name: named[1], msg: ERRORS[Number(named[2])]?.name === named[1] ? ERRORS[Number(named[2])].msg : named[3] };
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
  }