@render-foundation/utils 0.0.257 → 0.0.259

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 = new Date(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, 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, 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,
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(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'),
1680
+ sql `SUM(CASE WHEN burn.usdc_spent > 0 THEN burn.usdc_spent ELSE COALESCE(burn.from_escrow_usdc, 0) END)`.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)`)
@@ -1896,16 +1960,14 @@ export const pgClient = (config) => {
1896
1960
  // Check if dispersed_obligation table exists BEFORE the transaction so a missing table never
1897
1961
  // poisons the txn (PG aborts the entire transaction on any error, even a caught one).
1898
1962
  let hasObligationTable = false;
1963
+ let hasJsonbColumn = false;
1899
1964
  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
- }
1965
+ const { rows: tblRows } = await timedQuery(pgPool, `SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'dispersed_obligation' LIMIT 1`, [], 'checkObligationTable', log);
1966
+ hasObligationTable = tblRows.length > 0;
1967
+ const { rows: colRows } = await timedQuery(pgPool, `SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'dispersed_settlement' AND column_name = 'obligation_details' LIMIT 1`, [], 'checkObligationDetailsCol', log);
1968
+ hasJsonbColumn = colRows.length > 0;
1907
1969
  if (!hasObligationTable) {
1908
- log.warn(`dispersed obligations: table not found (migration 44 pending); skipping for tx ${p.txSignature}`);
1970
+ log.warn(`dispersed obligations: table not found (migration 44 pending); skipping fan-out for tx ${p.txSignature}`);
1909
1971
  }
1910
1972
  }
1911
1973
  // The outbox row is the durable, crash-safe anchor persisted BEFORE broadcast. When it carries a
@@ -1932,6 +1994,9 @@ export const pgClient = (config) => {
1932
1994
  correction_render: p.correctionRender ?? null,
1933
1995
  usage_from: p.usageFrom ?? null,
1934
1996
  usage_to: p.usageTo ?? null,
1997
+ ...(hasJsonbColumn && p.obligations?.length
1998
+ ? { obligation_details: JSON.stringify(p.obligations.map((o) => ({ uuid: o.uuid, amount_eur: o.amountEur, opened_at: o.openedAt.toISOString(), authorization_uuid: o.authorizationUuid }))) }
1999
+ : {}),
1935
2000
  })
1936
2001
  .onConflict((oc) => oc.column('tx_signature').doNothing())
1937
2002
  .executeTakeFirst();
@@ -1957,19 +2022,17 @@ export const pgClient = (config) => {
1957
2022
  }
1958
2023
  }
1959
2024
  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
- }
2025
+ await trx
2026
+ .insertInto('dispersed_obligation')
2027
+ .values(p.obligations.map((o) => ({
2028
+ settlement_tx_sig: p.txSignature,
2029
+ uuid: o.uuid,
2030
+ amount_eur: o.amountEur,
2031
+ opened_at: o.openedAt,
2032
+ authorization_uuid: o.authorizationUuid ?? null,
2033
+ })))
2034
+ .onConflict((oc) => oc.column('uuid').doNothing())
2035
+ .execute();
1973
2036
  }
1974
2037
  log.debug(`dispersed outbox tx ${p.txSignature}: ${res.numInsertedOrUpdatedRows} row(s), ${hasObligationTable ? (p.obligations?.length ?? 0) : 0} obligations`);
1975
2038
  return { inserted };
@@ -2022,10 +2085,11 @@ export const pgClient = (config) => {
2022
2085
  executedAt: r.executed_at,
2023
2086
  eurRender: r.eur_render ?? undefined,
2024
2087
  usdcSpent: r.usdc_spent ?? undefined,
2025
- // int8 comes back as a string from pg; block heights are well within JS safe-integer range.
2026
2088
  lastValidBlockHeight: r.last_valid_block_height != null
2027
2089
  ? Number(r.last_valid_block_height)
2028
2090
  : undefined,
2091
+ usageFrom: r.usage_from ?? undefined,
2092
+ usageTo: r.usage_to ?? undefined,
2029
2093
  }));
2030
2094
  },
2031
2095
  async voidDispersedOutbox(txSignature, log = consoleLogger) {