@render-foundation/utils 0.0.243 → 0.0.245

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.
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ var __rest = (this && this.__rest) || function (s, e) {
3
+ var t = {};
4
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
5
+ t[p] = s[p];
6
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
7
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
8
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
9
+ t[p[i]] = s[p[i]];
10
+ }
11
+ return t;
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.calculateBurnAmounts = exports.batchFinishedAtMedian = void 0;
15
+ const spl_utils_1 = require("@render-foundation/spl-utils");
16
+ const logger_1 = require("../logger");
17
+ // Utility function copied from cron-server
18
+ const batchFinishedAtMedian = (jobs) => {
19
+ const pricedAt = jobs.length % 2 == 0
20
+ ? new Date(Math.trunc((Date.parse(jobs[jobs.length / 2].finishedAt) +
21
+ Date.parse(jobs[jobs.length / 2 - 1].finishedAt)) /
22
+ 2))
23
+ : new Date(Date.parse(jobs[Math.floor(jobs.length / 2)].finishedAt));
24
+ return pricedAt;
25
+ };
26
+ exports.batchFinishedAtMedian = batchFinishedAtMedian;
27
+ /**
28
+ * Pure function that calculates burn amounts without side effects
29
+ * This is the core logic extracted from consumeIncrBuyAndBurnRenderPG
30
+ */
31
+ const calculateBurnAmounts = (input, opts = {
32
+ perpetual: true,
33
+ }, log = logger_1.consoleLogger) => {
34
+ const { jobs, userGrantSpends, burnAdjustments, params, decimals, priceAtDate, eurToUSDC, } = input;
35
+ // Apply grant/buy split logic to jobs
36
+ const processedJobs = jobs.map((j) => {
37
+ const { buyRenderAmt, grantRenderAmt } = j, rest = __rest(j, ["buyRenderAmt", "grantRenderAmt"]);
38
+ return rest;
39
+ }); // Deep copy to avoid mutations
40
+ for (const j of processedJobs) {
41
+ const userGrantSpend = userGrantSpends[j.userId];
42
+ if (userGrantSpend) {
43
+ if (opts.perpetual ||
44
+ userGrantSpend.userGrantSpend.perpetual ||
45
+ j.rndrUsed <= userGrantSpend.currAllot) {
46
+ j.grantRenderAmt = Number(j.rndrUsed);
47
+ log.debug(`job ${j.id} grant split: ${j.rndrUsed}`);
48
+ userGrantSpend.currAllot -= BigInt(j.rndrUsed);
49
+ }
50
+ else if (userGrantSpend.currAllot <= BigInt(0)) {
51
+ log.debug(`job ${j.id} buy split: ${j.rndrUsed}`);
52
+ j.buyRenderAmt = Number(j.rndrUsed);
53
+ }
54
+ else {
55
+ log.debug(`job ${j.id} buy + grant split: ${j.rndrUsed} grant: ${Number(userGrantSpend.currAllot)} buy: ${Number(j.rndrUsed) - Number(userGrantSpend.currAllot)}`);
56
+ j.grantRenderAmt = Number(userGrantSpend.currAllot);
57
+ j.buyRenderAmt = Number(j.rndrUsed) - Number(userGrantSpend.currAllot);
58
+ userGrantSpend.currAllot = BigInt(0);
59
+ }
60
+ }
61
+ else {
62
+ log.debug(`job ${j.id} no grant split: ${j.rndrUsed}`);
63
+ j.buyRenderAmt = Number(j.rndrUsed);
64
+ }
65
+ }
66
+ const totalRenderUsed = processedJobs.reduce((tot, job) => tot + Number(job.rndrUsed.toString()), 0);
67
+ log.debug(`jobs buy + grant split: ${JSON.stringify(processedJobs)}`);
68
+ // Check minimum threshold
69
+ if (totalRenderUsed < (0, spl_utils_1.toBN)(params.minTotalRndrThreshold, decimals).toNumber()) {
70
+ throw new Error(`totalRenderUsed ${totalRenderUsed / Math.pow(10, decimals)} < total threshold ${params.minTotalRndrThreshold}`);
71
+ }
72
+ const totalRenderBuyAndBurn = processedJobs.reduce((tot, job) => { var _a; return tot + Number((_a = job.buyRenderAmt) !== null && _a !== void 0 ? _a : 0); }, 0);
73
+ const totalRender = processedJobs.reduce((tot, job) => tot + Number(job.rndrUsed.toString()), 0);
74
+ log.debug(`Burn calculation inputs:`);
75
+ log.debug(` totalRenderUsed: ${totalRenderUsed}`);
76
+ log.debug(` totalRenderBuyAndBurn: ${totalRenderBuyAndBurn}`);
77
+ log.debug(` totalRender: ${totalRender}`);
78
+ log.debug(` priceAtDate: ${priceAtDate}`);
79
+ log.debug(` eurToUSDC: ${eurToUSDC}`);
80
+ const pricedAt = (0, exports.batchFinishedAtMedian)(processedJobs.map((job) => ({
81
+ finishedAt: job.completedAt.toISOString(),
82
+ jobId: job.id,
83
+ rndrUsed: Number(job.rndrUsed.toString()),
84
+ })));
85
+ // Core burn calculation logic
86
+ const toBurn = Math.trunc((totalRender / priceAtDate / 4) * 0.95 * eurToUSDC);
87
+ let renderToBuyAndBurn = Math.trunc((totalRenderBuyAndBurn / totalRender) * toBurn);
88
+ const origRenderToBuyAndBurn = renderToBuyAndBurn;
89
+ log.debug(`Core burn calculation:`);
90
+ log.debug(` toBurn: ${toBurn}`);
91
+ log.debug(` renderToBuyAndBurn: ${renderToBuyAndBurn}`);
92
+ log.debug(` origRenderToBuyAndBurn: ${origRenderToBuyAndBurn}`);
93
+ let burnAdjustmentId = undefined;
94
+ // Apply burn adjustments
95
+ if (burnAdjustments.length > 0) {
96
+ const adjustment = burnAdjustments[0];
97
+ burnAdjustmentId = adjustment.id;
98
+ const adjLeft = adjustment.downAdjToBurn - adjustment.adjusted;
99
+ const minBurnRndrThreshold = (0, spl_utils_1.toBN)(params.minBurnRndrThreshold, decimals).toNumber();
100
+ const available = renderToBuyAndBurn - minBurnRndrThreshold;
101
+ if (adjLeft > available) {
102
+ renderToBuyAndBurn = minBurnRndrThreshold;
103
+ }
104
+ else {
105
+ renderToBuyAndBurn -= adjLeft;
106
+ }
107
+ }
108
+ // Validate burn thresholds
109
+ const toBuyAndBurnUI = renderToBuyAndBurn / Math.pow(10, decimals);
110
+ if (renderToBuyAndBurn != 0) {
111
+ if (toBuyAndBurnUI > params.maxBurnRndrThreshold) {
112
+ throw new Error(`buy and burn RENDER ${toBuyAndBurnUI} too high`);
113
+ }
114
+ if (toBuyAndBurnUI < params.minBurnRndrThreshold) {
115
+ throw new Error(`buy and burn RENDER ${toBuyAndBurnUI} too low < ${params.minBurnRndrThreshold}`);
116
+ }
117
+ }
118
+ const renderToBurnFromEmissions = toBurn - origRenderToBuyAndBurn;
119
+ const totalToBurn = renderToBuyAndBurn + renderToBurnFromEmissions;
120
+ log.debug(`Final burn calculation results:`);
121
+ log.debug(` renderToBuyAndBurn: ${renderToBuyAndBurn}`);
122
+ log.debug(` renderToBurnFromEmissions: ${renderToBurnFromEmissions}`);
123
+ log.debug(` totalToBurn: ${totalToBurn}`);
124
+ log.debug(` toBurn: ${toBurn}`);
125
+ return {
126
+ totalRenderUsed,
127
+ totalRenderBuyAndBurn,
128
+ totalRender,
129
+ renderToBuyAndBurn,
130
+ renderToBurnFromEmissions,
131
+ totalToBurn,
132
+ toBurn,
133
+ origRenderToBuyAndBurn,
134
+ burnAdjustmentId,
135
+ pricedAt,
136
+ processedJobs,
137
+ };
138
+ };
139
+ exports.calculateBurnAmounts = calculateBurnAmounts;
140
+ //# sourceMappingURL=burnCalculations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"burnCalculations.js","sourceRoot":"","sources":["../../../src/burn/burnCalculations.ts"],"names":[],"mappings":"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"}
@@ -35,7 +35,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
35
35
  return (mod && mod.__esModule) ? mod : { "default": mod };
36
36
  };
37
37
  Object.defineProperty(exports, "__esModule", { value: true });
38
- exports.pgClient = exports.batchFinishedAtMedian = exports.foldGrantAllotment = exports.CREDIT_DECIMALS = exports.EUR_TO_CREDITS = exports.DISPERSED_BURN_CORRECTION_TABLE = exports.DISPERSED_SETTLEMENT_TABLE = exports.GRANT_BURN_TABLE = exports.GRANT_BURN_ID_TABLE = exports.OTOY_GRANT_TABLE = exports.USER_GRANT_SPEND_TABLE = exports.CURRENT_USER_GRANT_SPEND_TABLE = exports.POLYGON_UPGRADE_TABLE = exports.ENTITY_EPOCH_INFO_TABLE = exports.BURN_CORRECTION_TABLE = exports.NETWORK_REVENUE_TABLE = exports.BRIDGE_TRANSFER_TABLE = exports.BURN_TABLE = exports.JOB_TABLE = exports.JOB_ID_TABLE = exports.LIABILITY_ADJUSTMENT_BATCH_TABLE = exports.LIABILITY_ADJUSTMENT_TABLE = exports.LIABILITY_TABLE = exports.SOL_TRANSFER_TABLE = exports.ENTITY_TABLE = exports.MANUAL_BURN_TABLE = exports.EPOCH_TABLE = exports.SOL_TX_TABLE = void 0;
38
+ exports.pgClient = exports.batchFinishedAtMedian = exports.foldGrantAllotment = exports.CREDIT_DECIMALS = exports.GRANT_WINDOW_START = exports.EUR_TO_CREDITS = exports.DISPERSED_BURN_CORRECTION_TABLE = exports.DISPERSED_SETTLEMENT_TABLE = exports.GRANT_BURN_TABLE = exports.GRANT_BURN_ID_TABLE = exports.OTOY_GRANT_TABLE = exports.USER_GRANT_SPEND_TABLE = exports.CURRENT_USER_GRANT_SPEND_TABLE = exports.POLYGON_UPGRADE_TABLE = exports.ENTITY_EPOCH_INFO_TABLE = exports.BURN_CORRECTION_TABLE = exports.NETWORK_REVENUE_TABLE = exports.BRIDGE_TRANSFER_TABLE = exports.BURN_TABLE = exports.JOB_TABLE = exports.JOB_ID_TABLE = exports.LIABILITY_ADJUSTMENT_BATCH_TABLE = exports.LIABILITY_ADJUSTMENT_TABLE = exports.LIABILITY_TABLE = exports.SOL_TRANSFER_TABLE = exports.ENTITY_TABLE = exports.MANUAL_BURN_TABLE = exports.EPOCH_TABLE = exports.SOL_TX_TABLE = void 0;
39
39
  const kysely_1 = require("kysely");
40
40
  const logger_1 = require("../../logger");
41
41
  const pg_1 = require("pg");
@@ -71,6 +71,10 @@ BigInt.prototype.toJSON = function () {
71
71
  // confirmed_amount = EUR, despite the column name); 1 render credit = EUR 0.25, so credits = EUR x 4.
72
72
  // job.render_amt / user_grant_spend.render_spent_delta are credits x 1e8.
73
73
  exports.EUR_TO_CREDITS = BigInt(4);
74
+ // Grant accounting window: otoy_grant mirrors OTOY RFG issuances from this date. Consumption against the
75
+ // window counts EVERY job the user ran in it — buy-and-burn jobs consumed OTOY credits exactly like
76
+ // emissions-funded ones (counting only emissions-attributed spend over-issued ~2.58M credits, 2026-08-04).
77
+ exports.GRANT_WINDOW_START = '2025-05-01';
74
78
  exports.CREDIT_DECIMALS = BigInt(100000000); // 1e8
75
79
  /**
76
80
  * Pure core of the grant allotment: fold a user's OTOY grants against what's already been spent.
@@ -228,7 +232,7 @@ const pgClient = (config) => {
228
232
  // Spendable grant per user, straight off otoy_grant (FIFO), net of what's already been spent or
229
233
  // already settled by a burn correction. See GrantAllotment for the units + the double-spend guard.
230
234
  const getGrantAllotments = (db, p, log = logger_1.consoleLogger) => __awaiter(void 0, void 0, void 0, function* () {
231
- var _b, _c, _d;
235
+ var _b, _c, _d, _e;
232
236
  const out = {};
233
237
  if (!p.userIds.length)
234
238
  return out;
@@ -266,16 +270,32 @@ const pgClient = (config) => {
266
270
  .where('user_id', 'in', p.userIds)
267
271
  .groupBy('user_id')
268
272
  .execute();
269
- // jobs already settled by a burn correction their grant is spoken for (double-spend guard)
270
- const corrected = yield db
273
+ // ALL in-window job spend the true deduction ledger. Every render job flows through us, and every
274
+ // job consumed the user's OTOY credits regardless of HOW we funded the burn (emissions or USDC
275
+ // buy-and-burn). This supersedes the old correction-settled term (those jobs are in-window) and
276
+ // closes the pre-go-live gap where buy-and-burn jobs didn't decrement grants.
277
+ const jobspend = yield db
271
278
  .selectFrom(`${exports.JOB_TABLE} as j`)
272
- .innerJoin(`${exports.BURN_CORRECTION_TABLE} as bc`, 'bc.job_id', 'j.id')
273
279
  .select((eb) => ['j.user_id as user_id', eb.fn.sum('j.render_amt').as('amt')])
274
280
  .where('j.user_id', 'in', p.userIds)
281
+ .where('j.completed_at', '>=', new Date(exports.GRANT_WINDOW_START))
275
282
  .groupBy('j.user_id')
276
283
  .execute();
284
+ // legacy CSV CREDITED (pre-cutoff OTOY grants redeemed against in-window jobs): the FULL credited
285
+ // amount offsets job spend — those jobs were paid by pre-cutoff grants per OTOY's own redemption
286
+ // report. Using the floored remaining BALANCE here under-grants users whose emissions burns exceeded
287
+ // their credited amount (f5adcc66: balance 0 but credited 50,213 -> avail 6,390 vs correct 56,603).
288
+ const legacy = yield db
289
+ .selectFrom(exports.USER_GRANT_SPEND_TABLE)
290
+ .select((eb) => ['user_id', eb.fn.sum('render_spent_delta').as('v')])
291
+ .where('user_id', 'in', p.userIds)
292
+ .where('render_spent_delta', '>', '0')
293
+ .where('otoy_grant_id', 'is', null)
294
+ .groupBy('user_id')
295
+ .execute();
277
296
  const spentBy = new Map(spent.map((r) => { var _a; return [r.user_id, BigInt((_a = r.net) !== null && _a !== void 0 ? _a : 0)]; }));
278
- const corrBy = new Map(corrected.map((r) => { var _a; return [r.user_id, BigInt((_a = r.amt) !== null && _a !== void 0 ? _a : 0)]; }));
297
+ const jobBy = new Map(jobspend.map((r) => { var _a; return [r.user_id, BigInt((_a = r.amt) !== null && _a !== void 0 ? _a : 0)]; }));
298
+ const legBy = new Map(legacy.map((r) => { var _a; return [r.user_id, BigInt((_a = r.v) !== null && _a !== void 0 ? _a : 0)]; }));
279
299
  for (const g of grants) {
280
300
  const u = g.user_id;
281
301
  const cur = (_b = out[u]) !== null && _b !== void 0 ? _b : (out[u] = { userId: u, granted: BigInt(0), consumed: BigInt(0), avail: BigInt(0), grants: [] });
@@ -288,15 +308,25 @@ const pgClient = (config) => {
288
308
  createdAt: g.otoy_created_at ? new Date(g.otoy_created_at) : null,
289
309
  });
290
310
  }
311
+ // legacy-only users (no mirrored grant) still get an entry — their CSV balance is a granted source
312
+ for (const [u, v] of legBy) {
313
+ if (v > BigInt(0) && !out[u])
314
+ out[u] = { userId: u, granted: BigInt(0), consumed: BigInt(0), avail: BigInt(0), grants: [] };
315
+ }
291
316
  for (const u of Object.keys(out)) {
292
- const attributed = (_c = spentBy.get(u)) !== null && _c !== void 0 ? _c : BigInt(0); // abs() spend drawn or grant already credited
293
- const viaCorrection = (_d = corrBy.get(u)) !== null && _d !== void 0 ? _d : BigInt(0);
294
- const f = (0, exports.foldGrantAllotment)(out[u].grants, attributed, viaCorrection);
295
- out[u].granted = f.granted;
296
- out[u].consumed = f.consumed;
297
- out[u].avail = f.avail;
298
- if (viaCorrection > BigInt(0)) {
299
- log.info(`grant allotment ${u}: granted ${f.granted} - attributed ${attributed} - correction-settled ${viaCorrection} = ${f.avail}`);
317
+ const attributed = (_c = spentBy.get(u)) !== null && _c !== void 0 ? _c : BigInt(0); // ledger-counted (burns + pool-tied credits)
318
+ const spend = (_d = jobBy.get(u)) !== null && _d !== void 0 ? _d : BigInt(0); // ALL in-window job spend
319
+ const leg = (_e = legBy.get(u)) !== null && _e !== void 0 ? _e : BigInt(0);
320
+ // consumed = the larger of the two accountings — the ledger can exceed job spend (pool-tied
321
+ // credits retiring capacity), job spend can exceed the ledger (pre-go-live buy-and-burn jobs).
322
+ const consumed = attributed > spend ? attributed : spend;
323
+ const f = (0, exports.foldGrantAllotment)(out[u].grants, consumed, BigInt(0));
324
+ out[u].granted = f.granted + leg;
325
+ out[u].consumed = consumed;
326
+ out[u].avail =
327
+ f.granted + leg > consumed ? f.granted + leg - consumed : BigInt(0);
328
+ if (spend > attributed) {
329
+ log.info(`grant allotment ${u}: in-window job spend ${spend} > ledger-counted ${attributed} — strict consumption applied (granted ${f.granted} + legacy ${leg} => avail ${out[u].avail})`);
300
330
  }
301
331
  }
302
332
  return out;
@@ -1573,21 +1603,11 @@ const pgClient = (config) => {
1573
1603
  var _a;
1574
1604
  return __awaiter(this, void 0, void 0, function* () {
1575
1605
  const lastId = f.cursor ? atob(f.cursor) : undefined;
1576
- // grant_burn holds the portion of the SAME transaction burned from emissions rather than bought with
1577
- // USDC: markJobsBurnt writes buyBurn -> burn and grantBurn -> grant_burn, keyed on the same sol_tx_id.
1578
- // So burn.burned alone UNDERSTATES what the tx actually burned. Measured on prod: 2,053 grant_burn rows
1579
- // back to 2025-09, up to 23.9% of a month's total (Oct 2025) and ~1% recently.
1580
- //
1581
- // A correlated subquery, not a LEFT JOIN. Today there is at most one grant_burn per sol_tx (verified:
1582
- // max 1, zero sol_tx with more), but nothing enforces that, and a join that started multiplying rows
1583
- // would silently corrupt the limit and the cursor — the same failure the jobId filter avoids.
1584
- const grantBurned = (0, kysely_1.sql) `coalesce((select sum(gb.burned) from grant_burn gb where gb.sol_tx_id = burn.sol_tx_id), 0)`;
1585
1606
  let q = db.trx
1586
1607
  .selectFrom('burn')
1587
1608
  .selectAll('burn')
1588
1609
  .innerJoin('sol_tx', 'sol_tx.id', 'burn.sol_tx_id')
1589
- .select(['sol_tx.executed_at', 'sol_tx.sig'])
1590
- .select(grantBurned.as('grant_burned'));
1610
+ .select(['sol_tx.executed_at', 'sol_tx.sig']);
1591
1611
  if (!f.inclManual) {
1592
1612
  q = q.where('burn.usdc_spent', '>', String(0));
1593
1613
  }
@@ -1618,19 +1638,16 @@ const pgClient = (config) => {
1618
1638
  const comp = q.compile();
1619
1639
  const { rows } = yield (0, base_1.timedQuery)(pgPool, comp.sql, comp.parameters, 'fetchBurnsPage', log);
1620
1640
  const burns = rows.map((r) => {
1621
- var _a, _b, _c;
1641
+ var _a, _b;
1622
1642
  return ({
1623
1643
  jobs: [],
1624
- // The emissions-burned portion of the same tx. Callers wanting "RENDER burned by this transaction"
1625
- // must add this to `burned`.
1626
- grantBurned: BigInt((_a = r.grant_burned) !== null && _a !== void 0 ? _a : 0),
1627
1644
  id: Number(r.id),
1628
1645
  renderToUsdc: r.render_to_usdc,
1629
1646
  solTx: r.sig,
1630
1647
  executedAt: r.executed_at,
1631
1648
  pricedAt: r.priced_at,
1632
- usdcSpent: BigInt((_b = r.usdc_spent) !== null && _b !== void 0 ? _b : 0),
1633
- quoteAmt: BigInt((_c = r.quote_amt) !== null && _c !== void 0 ? _c : 0),
1649
+ usdcSpent: BigInt((_a = r.usdc_spent) !== null && _a !== void 0 ? _a : 0),
1650
+ quoteAmt: BigInt((_b = r.quote_amt) !== null && _b !== void 0 ? _b : 0),
1634
1651
  burned: BigInt(r.burned),
1635
1652
  eurToUsdc: r.eur_to_usdc,
1636
1653
  markedBurnedAt: r.marked_burned_at,
@@ -1671,16 +1688,12 @@ const pgClient = (config) => {
1671
1688
  // Truncating the naive column is pure arithmetic and identical under every session timezone, and
1672
1689
  // to_char hands back a plain 'YYYY-MM-DD' so node-postgres has no Date to decode in local time.
1673
1690
  const dayExpr = (0, kysely_1.sql) `to_char(date_trunc('day', sol_tx.executed_at), 'YYYY-MM-DD')`;
1674
- // Same emissions gap as fetchBurnsPage: summing burn.burned alone understates the daily total, so the
1675
- // charts drawn from this were short by the emissions portion too.
1676
- const grantBurnedDay = (0, kysely_1.sql) `coalesce(sum((select sum(gb.burned) from grant_burn gb where gb.sol_tx_id = burn.sol_tx_id)), 0)`;
1677
1691
  let q = db.trx
1678
1692
  .selectFrom('burn')
1679
1693
  .innerJoin('sol_tx', 'sol_tx.id', 'burn.sol_tx_id')
1680
1694
  .select(({ fn }) => [
1681
1695
  dayExpr.as('day'),
1682
1696
  fn.sum('burn.burned').as('render_burned'),
1683
- grantBurnedDay.as('grant_burned'),
1684
1697
  fn.sum('burn.usdc_spent').as('usdc_spent'),
1685
1698
  fn.count('burn.id').as('burn_count'),
1686
1699
  ])
@@ -1699,13 +1712,12 @@ const pgClient = (config) => {
1699
1712
  const { rows } = yield (0, base_1.timedQuery)(pgPool, comp.sql, comp.parameters, 'fetchDailyBurnStats', log);
1700
1713
  log.info(`fetchDailyBurnStats len ${rows.length}`);
1701
1714
  return rows.map((r) => {
1702
- var _a, _b, _c, _d;
1715
+ var _a, _b, _c;
1703
1716
  return ({
1704
1717
  day: r.day,
1705
1718
  renderBurned: BigInt((_a = r.render_burned) !== null && _a !== void 0 ? _a : 0),
1706
1719
  usdcSpent: BigInt((_b = r.usdc_spent) !== null && _b !== void 0 ? _b : 0),
1707
- grantBurned: BigInt((_c = r.grant_burned) !== null && _c !== void 0 ? _c : 0),
1708
- burnCount: Number((_d = r.burn_count) !== null && _d !== void 0 ? _d : 0),
1720
+ burnCount: Number((_c = r.burn_count) !== null && _c !== void 0 ? _c : 0),
1709
1721
  });
1710
1722
  });
1711
1723
  });