@render-foundation/utils 0.0.257 → 0.0.258

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.
@@ -1506,6 +1506,7 @@ export const pgClient = (config) => {
1506
1506
  eurToUsdc: r.eur_to_usdc,
1507
1507
  markedBurnedAt: r.marked_burned_at,
1508
1508
  tags: r.tags ? r.tags.split(',') : [],
1509
+ fromEscrowUsdc: BigInt(r.from_escrow_usdc ?? 0),
1509
1510
  });
1510
1511
  burnIdToIdx[r.id] = burns.length - 1;
1511
1512
  }
@@ -1528,70 +1529,133 @@ export const pgClient = (config) => {
1528
1529
  return burns;
1529
1530
  },
1530
1531
  async fetchBurnsPage(db, f, log = consoleLogger) {
1531
- const lastId = f.cursor ? atob(f.cursor) : undefined;
1532
- let q = db.trx
1533
- .selectFrom('burn')
1534
- .selectAll('burn')
1535
- .innerJoin('sol_tx', 'sol_tx.id', 'burn.sol_tx_id')
1536
- .select(['sol_tx.executed_at', 'sol_tx.sig']);
1537
- if (!f.inclManual) {
1538
- q = q.where('burn.usdc_spent', '>', String(0));
1539
- }
1540
- if (f.executedAfter) {
1541
- q = q.where('sol_tx.executed_at', '>=', f.executedAfter);
1542
- }
1543
- if (f.executedBefore) {
1544
- q = q.where('sol_tx.executed_at', '<=', f.executedBefore);
1545
- }
1546
- if (f.sig) {
1547
- // Exact match, not a prefix: sol_tx.sig is unique, so this is an index hit returning at most one row.
1548
- q = q.where('sol_tx.sig', '=', f.sig);
1532
+ const params = [];
1533
+ const p = (v) => {
1534
+ params.push(v);
1535
+ return `$${params.length}`;
1536
+ };
1537
+ // Decode cursor — supports new {t,id,ea} format and legacy bare-id format.
1538
+ let cursorEa;
1539
+ let cursorType;
1540
+ let cursorId;
1541
+ if (f.cursor) {
1542
+ const raw = atob(f.cursor);
1543
+ try {
1544
+ const c = JSON.parse(raw);
1545
+ cursorEa = c.ea;
1546
+ cursorType = c.t;
1547
+ cursorId = c.id;
1548
+ }
1549
+ catch {
1550
+ // Legacy cursor: bare burn.id — treat as buy-burn cursor.
1551
+ cursorId = Number(raw);
1552
+ cursorType = 'buy';
1553
+ }
1549
1554
  }
1555
+ // Shared WHERE fragments pushed into each union leg for index use.
1556
+ const dateFilters = [];
1557
+ if (f.executedAfter)
1558
+ dateFilters.push(`st.executed_at >= ${p(f.executedAfter)}`);
1559
+ if (f.executedBefore)
1560
+ dateFilters.push(`st.executed_at <= ${p(f.executedBefore)}`);
1561
+ const sigFilter = f.sig ? `st.sig = ${p(f.sig)}` : '';
1562
+ // Buy-burn leg
1563
+ const buyWhere = [...dateFilters];
1564
+ if (!f.inclManual)
1565
+ buyWhere.push('b.usdc_spent > 0');
1566
+ if (sigFilter)
1567
+ buyWhere.push(sigFilter);
1550
1568
  if (f.jobId !== undefined) {
1551
- // "Which burn consumed this job?" job.burn_id is the FK, so resolve it as a subquery rather than
1552
- // joining `job` into the page query, which would multiply rows by job count and break both the limit
1553
- // and the cursor.
1554
- q = q.where('burn.id', 'in', (eb) => eb
1555
- .selectFrom('job')
1556
- .select('job.burn_id')
1557
- .where('job.id', '=', String(f.jobId)));
1558
- }
1559
- if (lastId) {
1560
- q = q.where('burn.id', '>', lastId);
1569
+ buyWhere.push(`b.id IN (SELECT burn_id FROM job WHERE id = ${p(f.jobId)})`);
1570
+ }
1571
+ const buyWhereClause = buyWhere.length ? `WHERE ${buyWhere.join(' AND ')}` : '';
1572
+ const buySql = `
1573
+ SELECT 'buy'::text AS type, b.id::bigint AS sortid, b.id AS id,
1574
+ st.executed_at, st.executed_at::text AS executed_at_raw, b.priced_at, b.burned::text AS burned,
1575
+ b.usdc_spent::text AS usdc_spent, b.quote_amt::text AS quote_amt,
1576
+ b.tags, st.sig, b.render_to_usdc, b.eur_to_usdc,
1577
+ coalesce(b.from_escrow_usdc, 0)::text AS from_escrow_usdc,
1578
+ b.down_correction_id, b.escrow_correction_id,
1579
+ b.marked_burned_at
1580
+ FROM burn b
1581
+ INNER JOIN sol_tx st ON st.id = b.sol_tx_id
1582
+ ${buyWhereClause}`;
1583
+ // Grant-burn leg
1584
+ const grantWhere = [...dateFilters];
1585
+ if (sigFilter)
1586
+ grantWhere.push(sigFilter);
1587
+ if (f.jobId !== undefined) {
1588
+ grantWhere.push(`gb.id IN (SELECT grant_burn_id FROM job WHERE id = ${p(f.jobId)})`);
1589
+ }
1590
+ const grantWhereClause = grantWhere.length ? `WHERE ${grantWhere.join(' AND ')}` : '';
1591
+ const grantSql = `
1592
+ SELECT 'grant'::text AS type, gb.id::bigint AS sortid, gb.id AS id,
1593
+ st.executed_at, st.executed_at::text AS executed_at_raw, null::timestamp AS priced_at, gb.burned::text AS burned,
1594
+ '0' AS usdc_spent, '0' AS quote_amt,
1595
+ gb.tags, st.sig, null::float8 AS render_to_usdc, null::float8 AS eur_to_usdc,
1596
+ '0' AS from_escrow_usdc,
1597
+ null::bigint AS down_correction_id, null::bigint AS escrow_correction_id,
1598
+ gb.marked_burned_at
1599
+ FROM grant_burn gb
1600
+ INNER JOIN sol_tx st ON st.id = gb.sol_tx_id
1601
+ ${grantWhereClause}`;
1602
+ // Cursor filter on the outer query — row comparison for deterministic paging.
1603
+ let cursorFilter = '';
1604
+ if (cursorEa && cursorType && cursorId !== undefined) {
1605
+ cursorFilter = `WHERE (u.executed_at, u.type, u.sortid) > (${p(cursorEa)}, ${p(cursorType)}, ${p(cursorId)})`;
1606
+ }
1607
+ else if (cursorId !== undefined) {
1608
+ // Legacy cursor fallback: only buy burns, ordered by id.
1609
+ cursorFilter = `WHERE u.type = 'buy' AND u.sortid > ${p(cursorId)}`;
1561
1610
  }
1562
1611
  const limit = f.limit ?? 1000;
1563
- q = q.limit(limit).orderBy('burn.id');
1564
- const comp = q.compile();
1565
- const { rows } = await timedQuery(pgPool, comp.sql, comp.parameters, 'fetchBurnsPage', log);
1612
+ const sql = `
1613
+ SELECT u.* FROM (
1614
+ ${buySql}
1615
+ UNION ALL
1616
+ ${grantSql}
1617
+ ) u
1618
+ ${cursorFilter}
1619
+ ORDER BY u.executed_at ASC, u.type, u.sortid
1620
+ LIMIT ${p(limit)}`;
1621
+ const { rows } = await timedQuery(pgPool, sql, params, 'fetchBurnsPage', log);
1566
1622
  const burns = rows.map((r) => ({
1567
1623
  jobs: [],
1568
1624
  id: Number(r.id),
1569
- renderToUsdc: r.render_to_usdc,
1625
+ type: r.type,
1626
+ renderToUsdc: r.render_to_usdc ?? undefined,
1570
1627
  solTx: r.sig,
1571
1628
  executedAt: r.executed_at,
1572
- pricedAt: r.priced_at,
1629
+ pricedAt: r.priced_at ?? undefined,
1573
1630
  usdcSpent: BigInt(r.usdc_spent ?? 0),
1574
1631
  quoteAmt: BigInt(r.quote_amt ?? 0),
1575
1632
  burned: BigInt(r.burned),
1576
- eurToUsdc: r.eur_to_usdc,
1633
+ eurToUsdc: r.eur_to_usdc ?? undefined,
1577
1634
  markedBurnedAt: r.marked_burned_at,
1578
1635
  tags: r.tags ? r.tags.split(',') : [],
1636
+ fromEscrowUsdc: BigInt(r.from_escrow_usdc ?? 0),
1637
+ downCorrectionId: r.down_correction_id ? Number(r.down_correction_id) : undefined,
1638
+ escrowCorrectionId: r.escrow_correction_id ? Number(r.escrow_correction_id) : undefined,
1579
1639
  }));
1580
1640
  return {
1581
1641
  burns,
1582
- // Only hand back a cursor on a full page; a short page means we're done.
1583
1642
  cursor: burns.length === limit
1584
- ? btoa(String(burns[burns.length - 1].id))
1643
+ ? btoa(JSON.stringify({
1644
+ ea: rows[burns.length - 1].executed_at_raw,
1645
+ t: rows[burns.length - 1].type,
1646
+ id: rows[burns.length - 1].sortid,
1647
+ }))
1585
1648
  : undefined,
1586
1649
  };
1587
1650
  },
1588
- async fetchBurnJobs(db, burnId, log = consoleLogger) {
1651
+ async fetchBurnJobs(db, burnId, burnType = 'buy', log = consoleLogger) {
1652
+ const fk = burnType === 'grant' ? 'grant_burn_id' : 'burn_id';
1589
1653
  const rows = await db.trx
1590
1654
  .selectFrom('job')
1591
1655
  .selectAll('job')
1592
- .where('burn_id', '=', String(burnId))
1656
+ .where(fk, '=', String(burnId))
1593
1657
  .execute();
1594
- log.debug(`fetchBurnJobs burn ${burnId} -> ${rows.length} jobs`);
1658
+ log.debug(`fetchBurnJobs ${burnType} ${burnId} -> ${rows.length} jobs`);
1595
1659
  return rows.map((r) => ({
1596
1660
  id: Number(r.id),
1597
1661
  completedAt: r.completed_at,
@@ -1612,8 +1676,8 @@ export const pgClient = (config) => {
1612
1676
  .innerJoin('sol_tx', 'sol_tx.id', 'burn.sol_tx_id')
1613
1677
  .select(({ fn }) => [
1614
1678
  dayExpr.as('day'),
1615
- fn.sum('burn.burned').as('render_burned'),
1616
- fn.sum('burn.usdc_spent').as('usdc_spent'),
1679
+ sql `SUM(burn.burned + CASE 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'),
1680
+ sql `SUM(burn.usdc_spent + COALESCE(burn.from_escrow_usdc, 0))`.as('usdc_spent'),
1617
1681
  fn.count('burn.id').as('burn_count'),
1618
1682
  ])
1619
1683
  .groupBy(sql `date_trunc('day', sol_tx.executed_at)`)
@@ -1893,21 +1957,6 @@ export const pgClient = (config) => {
1893
1957
  });
1894
1958
  },
1895
1959
  async insertDispersedOutbox(p, log = consoleLogger) {
1896
- // Check if dispersed_obligation table exists BEFORE the transaction so a missing table never
1897
- // poisons the txn (PG aborts the entire transaction on any error, even a caught one).
1898
- let hasObligationTable = false;
1899
- if (p.obligations?.length) {
1900
- try {
1901
- const { rows } = await timedQuery(pgPool, `SELECT 1 FROM information_schema.tables WHERE table_name = 'dispersed_obligation' LIMIT 1`, [], 'checkObligationTable', log);
1902
- hasObligationTable = rows.length > 0;
1903
- }
1904
- catch {
1905
- hasObligationTable = false;
1906
- }
1907
- if (!hasObligationTable) {
1908
- log.warn(`dispersed obligations: table not found (migration 44 pending); skipping for tx ${p.txSignature}`);
1909
- }
1910
- }
1911
1960
  // The outbox row is the durable, crash-safe anchor persisted BEFORE broadcast. When it carries a
1912
1961
  // burn-correction draw, we increment consumed_render in the SAME transaction as the insert, bounded
1913
1962
  // so it can never exceed amount_render. Row + counter are therefore atomic and inseparable: no
@@ -1915,7 +1964,7 @@ export const pgClient = (config) => {
1915
1964
  // (decremented again by voidDispersedOutbox). If the bounded increment can't fit (a concurrent draw
1916
1965
  // took the room), the whole txn rolls back and we return {inserted:false} — the caller must NOT
1917
1966
  // broadcast (no row, no counter change, no burn).
1918
- const result = await db.transaction().execute(async (trx) => {
1967
+ return await db.transaction().execute(async (trx) => {
1919
1968
  const res = await trx
1920
1969
  .insertInto(DISPERSED_SETTLEMENT_TABLE)
1921
1970
  .values({
@@ -1956,29 +2005,13 @@ export const pgClient = (config) => {
1956
2005
  throw new DispersedCorrectionRejected();
1957
2006
  }
1958
2007
  }
1959
- if (inserted && hasObligationTable && p.obligations?.length) {
1960
- for (const o of p.obligations) {
1961
- await trx
1962
- .insertInto('dispersed_obligation')
1963
- .values({
1964
- settlement_tx_sig: p.txSignature,
1965
- uuid: o.uuid,
1966
- amount_eur: o.amountEur,
1967
- opened_at: o.openedAt,
1968
- authorization_uuid: o.authorizationUuid ?? null,
1969
- })
1970
- .onConflict((oc) => oc.column('uuid').doNothing())
1971
- .execute();
1972
- }
1973
- }
1974
- log.debug(`dispersed outbox tx ${p.txSignature}: ${res.numInsertedOrUpdatedRows} row(s), ${hasObligationTable ? (p.obligations?.length ?? 0) : 0} obligations`);
2008
+ log.debug(`dispersed outbox tx ${p.txSignature}: ${res.numInsertedOrUpdatedRows} row(s)`);
1975
2009
  return { inserted };
1976
2010
  }).catch((e) => {
1977
2011
  if (e instanceof DispersedCorrectionRejected)
1978
2012
  return { inserted: false };
1979
2013
  throw e;
1980
2014
  });
1981
- return result;
1982
2015
  },
1983
2016
  async getOutstandingDispersedCorrection(log = consoleLogger) {
1984
2017
  // consumed_render is the atomic source of truth; outstanding = amount_render - consumed_render.