@render-foundation/utils 0.0.254 → 0.0.255

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.foldGrantWindow = exports.GRANT_LOOKBACK_MS = exports.GRANT_LOOKBACK_DAYS = 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,36 +71,117 @@ 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
- /**
76
- * Pure core of the grant allotment: fold a user's OTOY grants against what's already been spent.
77
- *
78
- * consumed = attributedSpend (spend already stamped with an otoy_grant_id)
79
- * + correctionSettled (jobs whose grant is already being settled by a burn_correction)
80
- *
81
- * The correctionSettled term is the DOUBLE-SPEND GUARD: an escrow_credit correction means we're
82
- * recovering that job's dollars from escrow, so its grant is already committed. Counting it here stops
83
- * the same grant from also funding a future emissions burn.
84
- *
85
- * fifoGrantId = the oldest grant still holding room, i.e. the one the next spend draws from.
86
- */
87
- const foldGrantAllotment = (grants, attributedSpend, correctionSettled) => {
88
- const granted = grants.reduce((s, g) => s + g.amountCredits, BigInt(0));
89
- const consumed = (attributedSpend > BigInt(0) ? attributedSpend : BigInt(0)) +
90
- (correctionSettled > BigInt(0) ? correctionSettled : BigInt(0));
91
- const avail = granted > consumed ? granted - consumed : BigInt(0);
92
- let drawn = consumed;
79
+ // A grant is consumable for this long after issuance; whatever is left after that EXPIRES (2026-08-05
80
+ // model change). A job can only draw grants issued within the lookback window before it ran.
81
+ exports.GRANT_LOOKBACK_DAYS = 60;
82
+ exports.GRANT_LOOKBACK_MS = exports.GRANT_LOOKBACK_DAYS * 24 * 3600 * 1000;
83
+ const foldGrantWindow = (events, opts = {}) => {
84
+ var _a, _b, _c;
85
+ const lookback = (_a = opts.lookbackMs) !== null && _a !== void 0 ? _a : exports.GRANT_LOOKBACK_MS;
86
+ const now = (_b = opts.now) !== null && _b !== void 0 ? _b : Date.now();
87
+ const sorted = [...events].sort((a, b) => a.t - b.t || (a.amt > b.amt ? -1 : 1)); // credits first on ties
88
+ const buckets = [];
89
+ const jobs = [];
90
+ const remaining = (b) => b.amount - b.reversed - b.drawnEmissions - b.drawnMisrouted;
91
+ let beyond = BigInt(0);
92
+ for (const e of sorted) {
93
+ if (e.src === 'grant') {
94
+ if (e.amt > BigInt(0)) {
95
+ buckets.push({
96
+ grantId: e.grantId,
97
+ t: e.t,
98
+ amount: e.amt,
99
+ reversed: BigInt(0),
100
+ drawnEmissions: BigInt(0),
101
+ drawnMisrouted: BigInt(0),
102
+ expired: BigInt(0),
103
+ left: BigInt(0),
104
+ });
105
+ }
106
+ else {
107
+ let claw = -e.amt; // clawback hits remaining capacity oldest-first; excess is dropped
108
+ for (const b of buckets) {
109
+ if (claw <= BigInt(0))
110
+ break;
111
+ const take = remaining(b) < claw ? remaining(b) : claw;
112
+ if (take > BigInt(0)) {
113
+ b.reversed += take;
114
+ claw -= take;
115
+ }
116
+ }
117
+ }
118
+ }
119
+ else {
120
+ let need = -e.amt;
121
+ const total = need;
122
+ let emissionsLeft = (_c = e.emissions) !== null && _c !== void 0 ? _c : need; // no split provided -> count it all as emissions
123
+ let jobEm = BigInt(0);
124
+ let jobMis = BigInt(0);
125
+ for (const b of buckets) {
126
+ if (need <= BigInt(0))
127
+ break;
128
+ if (b.t + lookback < e.t)
129
+ continue; // grant expired before this job ran
130
+ const take = remaining(b) < need ? remaining(b) : need;
131
+ if (take <= BigInt(0))
132
+ continue;
133
+ const em = emissionsLeft < take ? emissionsLeft : take;
134
+ b.drawnEmissions += em;
135
+ b.drawnMisrouted += take - em;
136
+ jobEm += em;
137
+ jobMis += take - em;
138
+ emissionsLeft -= em;
139
+ need -= take;
140
+ }
141
+ beyond += need;
142
+ jobs.push({
143
+ id: e.id,
144
+ t: e.t,
145
+ need: total,
146
+ drawnEmissions: jobEm,
147
+ drawnMisrouted: jobMis,
148
+ beyond: need,
149
+ });
150
+ }
151
+ }
152
+ let avail = BigInt(0);
153
+ let expired = BigInt(0);
154
+ let usedEmissions = BigInt(0);
155
+ let usedMisrouted = BigInt(0);
93
156
  let fifoGrantId;
94
- for (const g of grants) {
95
- if (drawn < g.amountCredits) {
96
- fifoGrantId = g.id;
97
- break;
157
+ for (const b of buckets) {
158
+ usedEmissions += b.drawnEmissions;
159
+ usedMisrouted += b.drawnMisrouted;
160
+ const rem = remaining(b);
161
+ if (b.t + lookback < now) {
162
+ b.expired = rem;
163
+ }
164
+ else {
165
+ b.left = rem;
166
+ avail += rem;
167
+ if (fifoGrantId === undefined && rem > BigInt(0) && b.grantId !== undefined)
168
+ fifoGrantId = b.grantId;
98
169
  }
99
- drawn -= g.amountCredits;
170
+ expired += b.expired;
100
171
  }
101
- return { granted, consumed, avail, fifoGrantId };
172
+ return {
173
+ avail,
174
+ beyondGrants: beyond,
175
+ jobCharged: usedEmissions + usedMisrouted,
176
+ usedEmissions,
177
+ usedMisrouted,
178
+ expired,
179
+ buckets,
180
+ jobs,
181
+ fifoGrantId,
182
+ };
102
183
  };
103
- exports.foldGrantAllotment = foldGrantAllotment;
184
+ exports.foldGrantWindow = foldGrantWindow;
104
185
  const base_2 = require("./base");
105
186
  const moment_1 = __importDefault(require("moment"));
106
187
  pg.types.setTypeParser(1114, (str) => moment_1.default.utc(str).toDate());
@@ -228,66 +309,54 @@ const pgClient = (config) => {
228
309
  // Spendable grant per user, straight off otoy_grant (FIFO), net of what's already been spent or
229
310
  // already settled by a burn correction. See GrantAllotment for the units + the double-spend guard.
230
311
  const getGrantAllotments = (db, p, log = logger_1.consoleLogger) => __awaiter(void 0, void 0, void 0, function* () {
231
- var _b, _c, _d;
312
+ var _b, _c;
232
313
  const out = {};
233
314
  if (!p.userIds.length)
234
315
  return out;
235
- const grants = yield db
236
- .selectFrom(exports.OTOY_GRANT_TABLE)
237
- .select(['id', 'user_id', 'amount_credits', 'otoy_created_at'])
238
- .where('user_id', 'in', p.userIds)
239
- .orderBy('otoy_created_at', 'asc')
240
- .orderBy('id', 'asc')
241
- .execute();
242
- if (!grants.length)
243
- return out;
244
- // Anything already booked against a pool grant, in EITHER direction:
245
- // - negative delta = spend drawn from the grant (this mechanism)
246
- // - positive delta = the grant was CREDITED into current_user_grant_spend (the one-off canary
247
- // route). That balance is already handed to the caller separately, so leaving it in the pool too
248
- // would hand out the same grant twice.
249
- // Hence abs(): both directions retire pool capacity.
250
- const spent = yield db
251
- .selectFrom(exports.USER_GRANT_SPEND_TABLE)
252
- .select((eb) => [
253
- 'user_id',
254
- eb.fn.sum((0, kysely_1.sql) `abs(render_spent_delta)`).as('net'),
255
- ])
256
- .where('user_id', 'in', p.userIds)
257
- .where('otoy_grant_id', 'is not', null)
258
- .groupBy('user_id')
259
- .execute();
260
- // jobs already settled by a burn correction — their grant is spoken for (double-spend guard)
261
- const corrected = yield db
262
- .selectFrom(`${exports.JOB_TABLE} as j`)
263
- .innerJoin(`${exports.BURN_CORRECTION_TABLE} as bc`, 'bc.job_id', 'j.id')
264
- .select((eb) => ['j.user_id as user_id', eb.fn.sum('j.render_amt').as('amt')])
265
- .where('j.user_id', 'in', p.userIds)
266
- .groupBy('j.user_id')
267
- .execute();
268
- const spentBy = new Map(spent.map((r) => { var _a; return [r.user_id, BigInt((_a = r.net) !== null && _a !== void 0 ? _a : 0)]; }));
269
- const corrBy = new Map(corrected.map((r) => { var _a; return [r.user_id, BigInt((_a = r.amt) !== null && _a !== void 0 ? _a : 0)]; }));
270
- for (const g of grants) {
271
- const u = g.user_id;
272
- const cur = (_b = out[u]) !== null && _b !== void 0 ? _b : (out[u] = { userId: u, granted: BigInt(0), consumed: BigInt(0), avail: BigInt(0), grants: [] });
273
- // amount_credits is numeric EUR — truncate to whole base units after the x4 conversion
274
- const credits = (BigInt(Math.round(Number(g.amount_credits) * 1e8)) * exports.EUR_TO_CREDITS);
275
- cur.granted += credits;
276
- cur.grants.push({
277
- id: Number(g.id),
278
- amountCredits: credits,
279
- createdAt: g.otoy_created_at ? new Date(g.otoy_created_at) : null,
316
+ // Raw event stream, folded in code (foldGrantWindow — per-grant buckets, 60-day consumable window,
317
+ // reversals clamp capacity, only jobs create beyond-grants). SQL can't express the bucketed clamp.
318
+ const evRes = yield (0, kysely_1.sql) `
319
+ select user_id::text as u, coalesce(otoy_created_at, ${exports.GRANT_WINDOW_START}::timestamp) as t,
320
+ (amount_credits * 4 * 1e8)::numeric::text as amt, 'grant' as src, id as gid
321
+ from otoy_grant where user_id = any(${p.userIds}::uuid[])
322
+ union all
323
+ select user_id::text, ${exports.GRANT_WINDOW_START}::timestamp, sum(render_spent_delta)::text, 'grant', null
324
+ from user_grant_spend
325
+ where user_id = any(${p.userIds}::uuid[]) and render_spent_delta > 0 and otoy_grant_id is null
326
+ group by user_id
327
+ union all
328
+ select user_id::text, created_at, (-render_spent_delta)::text, 'grant', null
329
+ from user_grant_spend
330
+ where user_id = any(${p.userIds}::uuid[]) and render_spent_delta > 0 and otoy_grant_id is not null
331
+ union all
332
+ select user_id, completed_at, (-render_amt)::text, 'job', null
333
+ from job
334
+ where user_id = any(${p.userIds}::text[]) and completed_at >= ${exports.GRANT_WINDOW_START}::timestamp
335
+ `.execute(db); // db is always a Kysely trx at runtime
336
+ const byUser = new Map();
337
+ for (const r of evRes.rows) {
338
+ const arr = (_b = byUser.get(r.u)) !== null && _b !== void 0 ? _b : [];
339
+ arr.push({
340
+ t: new Date(r.t).getTime(),
341
+ amt: BigInt(Math.round(Number(r.amt))),
342
+ src: r.src,
343
+ grantId: (_c = r.gid) !== null && _c !== void 0 ? _c : undefined,
280
344
  });
345
+ byUser.set(r.u, arr);
281
346
  }
282
- for (const u of Object.keys(out)) {
283
- const attributed = (_c = spentBy.get(u)) !== null && _c !== void 0 ? _c : BigInt(0); // abs() — spend drawn or grant already credited
284
- const viaCorrection = (_d = corrBy.get(u)) !== null && _d !== void 0 ? _d : BigInt(0);
285
- const f = (0, exports.foldGrantAllotment)(out[u].grants, attributed, viaCorrection);
286
- out[u].granted = f.granted;
287
- out[u].consumed = f.consumed;
288
- out[u].avail = f.avail;
289
- if (viaCorrection > BigInt(0)) {
290
- log.info(`grant allotment ${u}: granted ${f.granted} - attributed ${attributed} - correction-settled ${viaCorrection} = ${f.avail}`);
347
+ for (const [u, events] of byUser) {
348
+ const f = (0, exports.foldGrantWindow)(events);
349
+ out[u] = {
350
+ userId: u,
351
+ granted: events.reduce((a, e) => (e.src === 'grant' && e.amt > BigInt(0) ? a + e.amt : a), BigInt(0)),
352
+ consumed: f.jobCharged,
353
+ avail: f.avail,
354
+ expired: f.expired,
355
+ fifoGrantId: f.fifoGrantId,
356
+ };
357
+ if (f.beyondGrants > BigInt(0)) {
358
+ log.info(`grant allotment ${u}: ${f.beyondGrants} beyond grants (purchase-paid at the time), ` +
359
+ `${f.expired} expired past the ${exports.GRANT_LOOKBACK_DAYS}d window — avail ${f.avail}`);
291
360
  }
292
361
  }
293
362
  return out;
@@ -372,7 +441,7 @@ const pgClient = (config) => {
372
441
  const sequenceNumbers = [];
373
442
  const dbEpochIds = [];
374
443
  for (const bt of bts) {
375
- let { solKey: toSolKey, seq, createdAt, amount, upgradeBracket, priorSupply, points, epochId, } = bt;
444
+ let { solKey: toSolKey, seq, createdAt, amount, upgradeBracket, priorSupply, points, epochId, sender, } = bt;
376
445
  let entityId;
377
446
  if (toSolKey && toSolKey != '') {
378
447
  entityId = yield getOrInsertEntity(db, { solKey: toSolKey });
@@ -393,6 +462,7 @@ const pgClient = (config) => {
393
462
  prior_supply: priorSupply,
394
463
  points: points,
395
464
  epoch_id: dbEpochId,
465
+ sender: sender,
396
466
  })
397
467
  .onConflict((oc) => oc.column('sequence').doUpdateSet({
398
468
  created_at: createdAt,
@@ -402,6 +472,7 @@ const pgClient = (config) => {
402
472
  points: points,
403
473
  epoch_id: dbEpochId,
404
474
  entity_id: entityId,
475
+ sender: sender,
405
476
  }))
406
477
  .returning('sequence');
407
478
  const c = q.compile();
@@ -1156,7 +1227,7 @@ const pgClient = (config) => {
1156
1227
  });
1157
1228
  },
1158
1229
  insertGrantSpend(db, p, log = logger_1.consoleLogger) {
1159
- var _a, _b, _c;
1230
+ var _a, _b, _c, _d;
1160
1231
  return __awaiter(this, void 0, void 0, function* () {
1161
1232
  const existing = yield getCurrentUserGrantSpend(db.trx, { userId: p.userId }, log);
1162
1233
  const diff = {
@@ -1183,16 +1254,13 @@ const pgClient = (config) => {
1183
1254
  return;
1184
1255
  }
1185
1256
  }
1186
- // FIFO-attribute this spend to the user's oldest OTOY grant that still has room. Stamping
1187
- // otoy_grant_id is what makes getGrantAllotments count it as consumed an unattributed row
1188
- // would leave the grant looking unspent and let it fund a second burn.
1257
+ // FIFO-attribute this spend to the user's oldest LIVE OTOY grant that still has room (the fold
1258
+ // already applies the lookback window). Stamping otoy_grant_id is what shows the draw in the
1259
+ // admin drill-down — an unattributed row would leave the grant looking unspent there.
1189
1260
  let otoyGrantId = p.otoyGrantId;
1190
1261
  if (otoyGrantId === undefined && p.renderSpentDelta < BigInt(0)) {
1191
1262
  const allots = yield getGrantAllotments(db.trx, { userIds: [p.userId] }, log);
1192
- const a = allots[p.userId];
1193
- if (a) {
1194
- otoyGrantId = (0, exports.foldGrantAllotment)(a.grants, a.consumed, BigInt(0)).fifoGrantId;
1195
- }
1263
+ otoyGrantId = (_c = allots[p.userId]) === null || _c === void 0 ? void 0 : _c.fifoGrantId;
1196
1264
  }
1197
1265
  const v = {
1198
1266
  user_id: p.userId,
@@ -1218,7 +1286,7 @@ const pgClient = (config) => {
1218
1286
  user_id: p.userId,
1219
1287
  render_spent: p.renderSpentDelta,
1220
1288
  description: p.description,
1221
- perpetual: (_c = p.perpetual) !== null && _c !== void 0 ? _c : false,
1289
+ perpetual: (_d = p.perpetual) !== null && _d !== void 0 ? _d : false,
1222
1290
  })
1223
1291
  .onConflict((oc) => oc.column('user_id').doUpdateSet(diff))
1224
1292
  .returning((eb) => [
@@ -1490,7 +1558,7 @@ const pgClient = (config) => {
1490
1558
  });
1491
1559
  },
1492
1560
  fetchBurns(db, f, log = logger_1.consoleLogger) {
1493
- var _a, _b;
1561
+ var _a, _b, _c;
1494
1562
  return __awaiter(this, void 0, void 0, function* () {
1495
1563
  let q = db.trx
1496
1564
  .selectFrom('burn')
@@ -1538,6 +1606,7 @@ const pgClient = (config) => {
1538
1606
  eurToUsdc: r.eur_to_usdc,
1539
1607
  markedBurnedAt: r.marked_burned_at,
1540
1608
  tags: r.tags ? r.tags.split(',') : [],
1609
+ fromEscrowUsdc: BigInt((_c = r.from_escrow_usdc) !== null && _c !== void 0 ? _c : 0),
1541
1610
  });
1542
1611
  burnIdToIdx[r.id] = burns.length - 1;
1543
1612
  }
@@ -1717,8 +1786,8 @@ const pgClient = (config) => {
1717
1786
  .innerJoin('sol_tx', 'sol_tx.id', 'burn.sol_tx_id')
1718
1787
  .select(({ fn }) => [
1719
1788
  dayExpr.as('day'),
1720
- fn.sum('burn.burned').as('render_burned'),
1721
- fn.sum('burn.usdc_spent').as('usdc_spent'),
1789
+ (0, kysely_1.sql) `SUM(CASE WHEN burn.burned > 0 THEN burn.burned WHEN COALESCE(burn.from_escrow_usdc, 0) > 0 AND burn.render_to_usdc > 0 THEN (burn.from_escrow_usdc::numeric / burn.render_to_usdc * 100)::bigint ELSE 0 END)`.as('render_burned'),
1790
+ (0, kysely_1.sql) `SUM(CASE WHEN burn.usdc_spent > 0 THEN burn.usdc_spent ELSE COALESCE(burn.from_escrow_usdc, 0) END)`.as('usdc_spent'),
1722
1791
  fn.count('burn.id').as('burn_count'),
1723
1792
  ])
1724
1793
  .groupBy((0, kysely_1.sql) `date_trunc('day', sol_tx.executed_at)`)
@@ -2026,7 +2095,7 @@ const pgClient = (config) => {
2026
2095
  // took the room), the whole txn rolls back and we return {inserted:false} — the caller must NOT
2027
2096
  // broadcast (no row, no counter change, no burn).
2028
2097
  return yield db.transaction().execute((trx) => __awaiter(this, void 0, void 0, function* () {
2029
- var _a, _b, _c, _d, _e, _f, _g;
2098
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
2030
2099
  const res = yield trx
2031
2100
  .insertInto(exports.DISPERSED_SETTLEMENT_TABLE)
2032
2101
  .values({
@@ -2041,10 +2110,12 @@ const pgClient = (config) => {
2041
2110
  usdc_spent: (_c = p.usdcSpent) !== null && _c !== void 0 ? _c : null,
2042
2111
  correction_id: (_d = p.correctionId) !== null && _d !== void 0 ? _d : null,
2043
2112
  correction_render: (_e = p.correctionRender) !== null && _e !== void 0 ? _e : null,
2113
+ usage_from: (_f = p.usageFrom) !== null && _f !== void 0 ? _f : null,
2114
+ usage_to: (_g = p.usageTo) !== null && _g !== void 0 ? _g : null,
2044
2115
  })
2045
2116
  .onConflict((oc) => oc.column('tx_signature').doNothing())
2046
2117
  .executeTakeFirst();
2047
- const inserted = ((_f = res.numInsertedOrUpdatedRows) !== null && _f !== void 0 ? _f : BigInt(0)) > BigInt(0);
2118
+ const inserted = ((_h = res.numInsertedOrUpdatedRows) !== null && _h !== void 0 ? _h : BigInt(0)) > BigInt(0);
2048
2119
  // Only reserve when the row was actually inserted (a tx_signature conflict is a no-op → no double
2049
2120
  // count) and it drew a correction.
2050
2121
  if (inserted &&
@@ -2059,7 +2130,7 @@ const pgClient = (config) => {
2059
2130
  .where('id', '=', String(p.correctionId))
2060
2131
  .where((eb) => eb((0, kysely_1.sql) `consumed_render + ${p.correctionRender}::numeric`, '<=', eb.ref('amount_render')))
2061
2132
  .executeTakeFirst();
2062
- if (((_g = upd.numUpdatedRows) !== null && _g !== void 0 ? _g : BigInt(0)) === BigInt(0)) {
2133
+ if (((_j = upd.numUpdatedRows) !== null && _j !== void 0 ? _j : BigInt(0)) === BigInt(0)) {
2063
2134
  // Would exceed amount_render (concurrent draw won the room) — roll back the whole txn.
2064
2135
  log.warn(`dispersed correction ${p.correctionId}: draw ${p.correctionRender} would over-consume; rejecting outbox insert for tx ${p.txSignature}`);
2065
2136
  throw new DispersedCorrectionRejected();