tapimo 0.8.0 → 0.10.0
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.
- package/dist/App.d.ts +7410 -7613
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +0 -1
- package/dist/App.js.map +1 -1
- package/dist/Webhooks.d.ts +21 -20
- package/dist/Webhooks.d.ts.map +1 -1
- package/dist/Webhooks.js +66 -23
- package/dist/Webhooks.js.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +74 -74
- package/dist/apps/Zones.d.ts +34 -22
- package/dist/apps/Zones.d.ts.map +1 -1
- package/dist/apps/Zones.js +9 -8
- package/dist/apps/Zones.js.map +1 -1
- package/dist/apps/data/App.d.ts +3120 -2782
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/FxOracle.d.ts +4 -5
- package/dist/apps/data/FxOracle.d.ts.map +1 -1
- package/dist/apps/data/FxOracle.js +19 -4
- package/dist/apps/data/FxOracle.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +32 -27
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/addresses.d.ts +283 -52
- package/dist/apps/data/routes/addresses.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +291 -21
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +232 -66
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/blocks.d.ts +4 -4
- package/dist/apps/data/routes/coingecko.d.ts +54 -54
- package/dist/apps/data/routes/earn.d.ts +642 -126
- package/dist/apps/data/routes/earn.d.ts.map +1 -1
- package/dist/apps/data/routes/earn.js +627 -58
- package/dist/apps/data/routes/earn.js.map +1 -1
- package/dist/apps/data/routes/exchanges.d.ts +120 -120
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
- package/dist/apps/data/routes/exchanges.js +114 -118
- package/dist/apps/data/routes/exchanges.js.map +1 -1
- package/dist/apps/data/routes/receipts.d.ts.map +1 -1
- package/dist/apps/data/routes/receipts.js +68 -47
- package/dist/apps/data/routes/receipts.js.map +1 -1
- package/dist/apps/data/routes/tokens.d.ts +2 -0
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/transactions.d.ts +59 -51
- package/dist/apps/data/routes/transactions.d.ts.map +1 -1
- package/dist/apps/data/routes/transactions.js +25 -23
- package/dist/apps/data/routes/transactions.js.map +1 -1
- package/dist/apps/data/routes/transfers.d.ts +40 -18
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +618 -105
- package/dist/apps/data/routes/transfers.js.map +1 -1
- package/dist/apps/data/routes/valuation.d.ts +21 -21
- package/dist/apps/data/routes/valuation.d.ts.map +1 -1
- package/dist/apps/data/routes/valuation.js +2 -2
- package/dist/apps/data/routes/valuation.js.map +1 -1
- package/dist/apps/data/routes/verified-tokens.d.ts +37 -37
- package/dist/apps/data/routes/webhooks.d.ts +102 -505
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +484 -342
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/data/routes/zones.d.ts +12 -12
- package/dist/apps/data/routes/zones.d.ts.map +1 -1
- package/dist/apps/data/routes/zones.js +3 -1
- package/dist/apps/data/routes/zones.js.map +1 -1
- package/dist/apps/funding/App.d.ts +127 -386
- package/dist/apps/funding/App.d.ts.map +1 -1
- package/dist/apps/funding/App.js +20 -5
- package/dist/apps/funding/App.js.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.d.ts +141 -532
- package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.js +21 -103
- package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
- package/dist/apps/funding/routes/transfers.d.ts +4 -4
- package/dist/apps/funding/routes/transfers.js +5 -5
- package/dist/apps/funding/routes/transfers.js.map +1 -1
- package/dist/apps/management/App.d.ts +429 -727
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +24 -24
- package/dist/apps/management/routes/billing.d.ts +72 -72
- package/dist/apps/management/routes/faucet.d.ts +6 -6
- package/dist/apps/management/routes/invite-links.d.ts +38 -38
- package/dist/apps/management/routes/me.d.ts +6 -6
- package/dist/apps/management/routes/members.d.ts +30 -30
- package/dist/apps/management/routes/projects.d.ts +20 -20
- package/dist/apps/management/routes/usage.d.ts +24 -24
- package/dist/apps/management/routes/webhooks.d.ts +158 -573
- package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/mpp/App.d.ts +12 -0
- package/dist/apps/mpp/App.d.ts.map +1 -1
- package/dist/apps/mpp/Mpp.d.ts +3 -0
- package/dist/apps/mpp/Mpp.d.ts.map +1 -1
- package/dist/apps/mpp/Mpp.js +3 -0
- package/dist/apps/mpp/Mpp.js.map +1 -1
- package/dist/apps/mpp/internal/Adapter.d.ts.map +1 -1
- package/dist/apps/mpp/internal/Adapter.js +9 -2
- package/dist/apps/mpp/internal/Adapter.js.map +1 -1
- package/dist/db/Db.d.ts +2 -2
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +25 -15
- package/dist/db/Db.js.map +1 -1
- package/dist/db/tables/fundingDeposits.d.ts +18 -1
- package/dist/db/tables/fundingDeposits.d.ts.map +1 -1
- package/dist/db/tables/fundingDeposits.js +46 -2
- package/dist/db/tables/fundingDeposits.js.map +1 -1
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
- package/dist/db/tables/sponsoredTransactions.js +4 -1
- package/dist/db/tables/sponsoredTransactions.js.map +1 -1
- package/dist/db/tables/webhookSubscriptions.d.ts +56 -3
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
- package/dist/db/tables/webhookSubscriptions.js +76 -3
- package/dist/db/tables/webhookSubscriptions.js.map +1 -1
- package/dist/internal/Auth.d.ts +4 -0
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +11 -2
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/MetricSink.d.ts +112 -3
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +116 -3
- package/dist/internal/MetricSink.js.map +1 -1
- package/dist/internal/Schema.js +1 -1
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/Tidx.d.ts +1 -3
- package/dist/internal/Tidx.d.ts.map +1 -1
- package/dist/internal/Tidx.js +1 -3
- package/dist/internal/Tidx.js.map +1 -1
- package/dist/internal/Viem.d.ts.map +1 -1
- package/dist/internal/Viem.js +2 -1
- package/dist/internal/Viem.js.map +1 -1
- package/dist/internal/WebhookDestination.d.ts +2 -0
- package/dist/internal/WebhookDestination.d.ts.map +1 -1
- package/dist/internal/WebhookDestination.js +11 -0
- package/dist/internal/WebhookDestination.js.map +1 -1
- package/dist/internal/WebhookTransfer.d.ts +55 -0
- package/dist/internal/WebhookTransfer.d.ts.map +1 -0
- package/dist/internal/WebhookTransfer.js +296 -0
- package/dist/internal/WebhookTransfer.js.map +1 -0
- package/dist/internal/Webhooks.d.ts +37 -0
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +87 -13
- package/dist/internal/Webhooks.js.map +1 -1
- package/dist/internal/funding/Provider.d.ts +10 -6
- package/dist/internal/funding/Provider.d.ts.map +1 -1
- package/dist/internal/funding/Provider.js +11 -7
- package/dist/internal/funding/Provider.js.map +1 -1
- package/dist/internal/funding/Reconciliation.d.ts.map +1 -1
- package/dist/internal/funding/Reconciliation.js +229 -16
- package/dist/internal/funding/Reconciliation.js.map +1 -1
- package/dist/internal/funding/providers/relay.d.ts +3 -0
- package/dist/internal/funding/providers/relay.d.ts.map +1 -1
- package/dist/internal/funding/providers/relay.js +18 -3
- package/dist/internal/funding/providers/relay.js.map +1 -1
- package/package.json +3 -3
- package/src/App.test.ts +32 -0
- package/src/App.ts +4 -1
- package/src/Client.test-d.ts +116 -38
- package/src/Webhooks.bench.ts +714 -0
- package/src/Webhooks.memory.bench.ts +250 -0
- package/src/Webhooks.test.ts +77 -6
- package/src/Webhooks.ts +83 -29
- package/src/apps/Zones.test.ts +5 -4
- package/src/apps/Zones.ts +9 -8
- package/src/apps/composition.test.ts +39 -10
- package/src/apps/data/FxOracle.test.ts +18 -0
- package/src/apps/data/FxOracle.ts +28 -5
- package/src/apps/data/routes/activities.test.ts +19 -0
- package/src/apps/data/routes/activities.ts +34 -30
- package/src/apps/data/routes/balances.test.ts +161 -10
- package/src/apps/data/routes/balances.ts +339 -147
- package/src/apps/data/routes/blocks.test.ts +12 -6
- package/src/apps/data/routes/earn.test.ts +286 -9
- package/src/apps/data/routes/earn.ts +841 -58
- package/src/apps/data/routes/exchanges.test.ts +7 -6
- package/src/apps/data/routes/exchanges.ts +124 -117
- package/src/apps/data/routes/receipts.test.ts +24 -7
- package/src/apps/data/routes/receipts.ts +67 -50
- package/src/apps/data/routes/tokens.test.ts +51 -0
- package/src/apps/data/routes/transactions.test.ts +42 -0
- package/src/apps/data/routes/transactions.ts +44 -39
- package/src/apps/data/routes/transfers.test.ts +402 -24
- package/src/apps/data/routes/transfers.ts +708 -101
- package/src/apps/data/routes/valuation.test.ts +26 -0
- package/src/apps/data/routes/valuation.ts +6 -2
- package/src/apps/data/routes/webhooks.test.ts +414 -130
- package/src/apps/data/routes/webhooks.ts +603 -419
- package/src/apps/data/routes/zones.ts +6 -1
- package/src/apps/funding/App.ts +20 -5
- package/src/apps/funding/routes/deposit-addresses.test.ts +43 -22
- package/src/apps/funding/routes/deposit-addresses.ts +34 -117
- package/src/apps/funding/routes/quotes.test.ts +1 -1
- package/src/apps/funding/routes/transfers.test.ts +6 -25
- package/src/apps/funding/routes/transfers.ts +5 -5
- package/src/apps/mpp/Mpp.test.ts +13 -1
- package/src/apps/mpp/Mpp.ts +8 -0
- package/src/apps/mpp/internal/Adapter.ts +10 -2
- package/src/db/Db.test.ts +40 -6
- package/src/db/Db.ts +42 -19
- package/src/db/tables/fundingDeposits.test.ts +101 -0
- package/src/db/tables/fundingDeposits.ts +71 -2
- package/src/db/tables/sponsoredTransactions.ts +9 -2
- package/src/db/tables/webhookSubscriptions.ts +116 -4
- package/src/internal/Auth.test.ts +28 -0
- package/src/internal/Auth.ts +15 -1
- package/src/internal/EarnVaults.test.ts +2 -2
- package/src/internal/MetricSink.test.ts +518 -0
- package/src/internal/MetricSink.ts +266 -9
- package/src/internal/Schema.ts +1 -1
- package/src/internal/Tidx.test.ts +4 -4
- package/src/internal/Tidx.ts +1 -3
- package/src/internal/Viem.test.ts +8 -0
- package/src/internal/Viem.ts +2 -1
- package/src/internal/WebhookDestination.test.ts +20 -0
- package/src/internal/WebhookDestination.ts +10 -0
- package/src/internal/WebhookTransfer.ts +426 -0
- package/src/internal/Webhooks.test.ts +137 -0
- package/src/internal/Webhooks.ts +131 -17
- package/src/internal/funding/Provider.test.ts +47 -7
- package/src/internal/funding/Provider.ts +16 -11
- package/src/internal/funding/Reconciliation.local.test.ts +3 -1
- package/src/internal/funding/Reconciliation.test.ts +1532 -54
- package/src/internal/funding/Reconciliation.ts +296 -53
- package/src/internal/funding/SourceObservation.test.ts +3 -2
- package/src/internal/funding/providers/relay.test.ts +1 -4
- package/src/internal/funding/providers/relay.ts +23 -3
|
@@ -553,22 +553,28 @@ export function receipts(options: receipts.Options = {}) {
|
|
|
553
553
|
|
|
554
554
|
// Value each fee via its own currency tag, in the requested
|
|
555
555
|
// denomination.
|
|
556
|
-
const denomination = query['valuation.currency']
|
|
557
|
-
const rates =
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
556
|
+
const denomination = query['valuation.currency']
|
|
557
|
+
const rates = denomination
|
|
558
|
+
? await Valuation.ratesFor(c, {
|
|
559
|
+
currencies: receipts.data.map((receipt) => receipt.feeAmount.currency),
|
|
560
|
+
denomination,
|
|
561
|
+
oracle,
|
|
562
|
+
})
|
|
563
|
+
: undefined
|
|
562
564
|
const data = receipts.data.map((receipt) => ({
|
|
563
565
|
...receipt,
|
|
564
566
|
feeAmount: {
|
|
565
567
|
...receipt.feeAmount,
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
568
|
+
...(denomination
|
|
569
|
+
? {
|
|
570
|
+
valuation: Valuation.valuationFor({
|
|
571
|
+
amount: BigInt(receipt.feeAmount.baseUnits),
|
|
572
|
+
denomination,
|
|
573
|
+
rates,
|
|
574
|
+
token: receipt.feeAmount,
|
|
575
|
+
}),
|
|
576
|
+
}
|
|
577
|
+
: {}),
|
|
572
578
|
},
|
|
573
579
|
}))
|
|
574
580
|
|
|
@@ -680,22 +686,28 @@ export function receipts(options: receipts.Options = {}) {
|
|
|
680
686
|
|
|
681
687
|
// Value the fee via its own currency tag, in the requested
|
|
682
688
|
// denomination.
|
|
683
|
-
const denomination = query['valuation.currency']
|
|
684
|
-
const rates =
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
+
const denomination = query['valuation.currency']
|
|
690
|
+
const rates = denomination
|
|
691
|
+
? await Valuation.ratesFor(c, {
|
|
692
|
+
currencies: [enriched.feeAmount.currency],
|
|
693
|
+
denomination,
|
|
694
|
+
oracle,
|
|
695
|
+
})
|
|
696
|
+
: undefined
|
|
689
697
|
const valued = {
|
|
690
698
|
...enriched,
|
|
691
699
|
feeAmount: {
|
|
692
700
|
...enriched.feeAmount,
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
701
|
+
...(denomination
|
|
702
|
+
? {
|
|
703
|
+
valuation: Valuation.valuationFor({
|
|
704
|
+
amount: BigInt(enriched.feeAmount.baseUnits),
|
|
705
|
+
denomination,
|
|
706
|
+
rates,
|
|
707
|
+
token: enriched.feeAmount,
|
|
708
|
+
}),
|
|
709
|
+
}
|
|
710
|
+
: {}),
|
|
699
711
|
},
|
|
700
712
|
meta: {
|
|
701
713
|
...enriched.meta,
|
|
@@ -771,9 +783,9 @@ export function getReceipts(c: Context<App.Environment>, options: getReceipts.Op
|
|
|
771
783
|
return Timing.time(c, 'receipts', () =>
|
|
772
784
|
Store.memoize(
|
|
773
785
|
async () => {
|
|
774
|
-
// Page the `receipts` table alone
|
|
775
|
-
//
|
|
776
|
-
//
|
|
786
|
+
// Page the `receipts` table alone. Fee-token queries read metadata
|
|
787
|
+
// denormalized on ClickHouse; other queries hydrate it from `txs`.
|
|
788
|
+
// Paging straight off `receipts` avoids the
|
|
777
789
|
// `receipts JOIN txs` shape, which TIDX's planner cannot execute under
|
|
778
790
|
// address scope: a `UNION` of two JOINs (or the `(from = X OR to = X)`
|
|
779
791
|
// form) reliably returns `db error` (HTTP 422) for high-traffic
|
|
@@ -785,10 +797,9 @@ export function getReceipts(c: Context<App.Environment>, options: getReceipts.Op
|
|
|
785
797
|
if (sender !== undefined) filters.push(`"from" = '${sender}'`)
|
|
786
798
|
if (recipient !== undefined) filters.push(`"to" = '${recipient}'`)
|
|
787
799
|
if (feePayer !== undefined) filters.push(`fee_payer = '${feePayer}'`)
|
|
788
|
-
//
|
|
789
|
-
//
|
|
790
|
-
if (feeToken !== undefined)
|
|
791
|
-
filters.push(`tx_hash IN (SELECT hash FROM txs WHERE fee_token = '${feeToken}')`)
|
|
800
|
+
// ClickHouse receipts denormalize fee metadata specifically to avoid
|
|
801
|
+
// an unbounded transaction-hash semi-join for this filter.
|
|
802
|
+
if (feeToken !== undefined) filters.push(`fee_token = '${feeToken}'`)
|
|
792
803
|
if (fromBlock !== undefined) filters.push(`block_num >= ${fromBlock}`)
|
|
793
804
|
if (toBlock !== undefined) filters.push(`block_num <= ${toBlock}`)
|
|
794
805
|
if (fromTimestamp !== undefined) filters.push(`block_timestamp >= '${fromTimestamp}'`)
|
|
@@ -804,7 +815,7 @@ export function getReceipts(c: Context<App.Environment>, options: getReceipts.Op
|
|
|
804
815
|
|
|
805
816
|
const columns = `block_num, block_timestamp, tx_idx, tx_hash, "from", "to",
|
|
806
817
|
contract_address, gas_used, cumulative_gas_used, effective_gas_price,
|
|
807
|
-
status, fee_payer`
|
|
818
|
+
status, fee_payer${feeToken === undefined ? '' : ', type, fee_token'}`
|
|
808
819
|
|
|
809
820
|
// For address-scoped queries, run one branch per side and `UNION` so
|
|
810
821
|
// each branch returns at most `limit + 1` index-ordered rows, the outer
|
|
@@ -814,14 +825,15 @@ export function getReceipts(c: Context<App.Environment>, options: getReceipts.Op
|
|
|
814
825
|
//
|
|
815
826
|
// The `receipts` table only indexes `from`, not `to` (a `to` scan over
|
|
816
827
|
// ~25M rows hits the indexer's statement timeout and returns `db error`,
|
|
817
|
-
// HTTP 422).
|
|
818
|
-
//
|
|
819
|
-
// `to`
|
|
828
|
+
// HTTP 422). A fee-payer filter bounds the direct `to` scan through the
|
|
829
|
+
// receipt fee-payer index. Other `to` branches resolve matching hashes
|
|
830
|
+
// through the indexed `txs.to` and `receipts.tx_hash` columns.
|
|
820
831
|
const sideQuery = (side: 'from' | 'to', addr: string) => {
|
|
821
|
-
const predicate =
|
|
822
|
-
side === 'from'
|
|
823
|
-
|
|
824
|
-
|
|
832
|
+
const predicate = (() => {
|
|
833
|
+
if (side === 'from') return `"from" = '${addr}'`
|
|
834
|
+
if (feePayer !== undefined) return `"to" = '${addr}'`
|
|
835
|
+
return `tx_hash IN (SELECT hash FROM txs WHERE "to" = '${addr}')`
|
|
836
|
+
})()
|
|
825
837
|
const where = [predicate, ...filters].join(' AND ')
|
|
826
838
|
// In the offset lane each branch over-fetches the full window
|
|
827
839
|
// (`offset + limit + 1` rows) because the skip applies to the merged
|
|
@@ -847,7 +859,11 @@ export function getReceipts(c: Context<App.Environment>, options: getReceipts.Op
|
|
|
847
859
|
LIMIT ${limit + 1}${skip}
|
|
848
860
|
`
|
|
849
861
|
|
|
850
|
-
const result = await tidx.fetch({
|
|
862
|
+
const result = await tidx.fetch({
|
|
863
|
+
chainId,
|
|
864
|
+
...(feeToken !== undefined ? { engine: 'clickhouse' } : {}),
|
|
865
|
+
query: query as string,
|
|
866
|
+
})
|
|
851
867
|
|
|
852
868
|
// The next page anchors below the last fetched row's `(block, tx_idx)`.
|
|
853
869
|
const page = Cursor.paginate({
|
|
@@ -860,11 +876,8 @@ export function getReceipts(c: Context<App.Environment>, options: getReceipts.Op
|
|
|
860
876
|
},
|
|
861
877
|
})
|
|
862
878
|
|
|
863
|
-
// Enrich
|
|
864
|
-
//
|
|
865
|
-
// sorted by `(block_num, idx)` with no hash index, so the hash list
|
|
866
|
-
// must be paired with the page's block numbers — a bare `hash IN`
|
|
867
|
-
// full-scans the table.
|
|
879
|
+
// Enrich non-fee-token pages with metadata from `txs`. Pairing hashes
|
|
880
|
+
// with their blocks avoids a full-table hash scan.
|
|
868
881
|
const hashes = [
|
|
869
882
|
...new Set(
|
|
870
883
|
page.rows
|
|
@@ -880,7 +893,7 @@ export function getReceipts(c: Context<App.Environment>, options: getReceipts.Op
|
|
|
880
893
|
),
|
|
881
894
|
]
|
|
882
895
|
const txByHash = new Map<string, { feeToken: unknown; type: unknown }>()
|
|
883
|
-
if (hashes.length > 0 && blocks.length > 0) {
|
|
896
|
+
if (feeToken === undefined && hashes.length > 0 && blocks.length > 0) {
|
|
884
897
|
const txs = await tidx.fetch({
|
|
885
898
|
chainId,
|
|
886
899
|
query: `SELECT hash, type, fee_token FROM txs WHERE block_num IN (${blocks.join(
|
|
@@ -900,7 +913,8 @@ export function getReceipts(c: Context<App.Environment>, options: getReceipts.Op
|
|
|
900
913
|
const data: humanizeReceipt.Output[] = []
|
|
901
914
|
for (const row of page.rows) {
|
|
902
915
|
const tx = typeof row['tx_hash'] === 'string' ? txByHash.get(row['tx_hash']) : undefined
|
|
903
|
-
const merged =
|
|
916
|
+
const merged =
|
|
917
|
+
feeToken === undefined ? { ...row, fee_token: tx?.feeToken, type: tx?.type } : row
|
|
904
918
|
const rpc = schema.Rpc.Receipt.safeParse(toRpcReceipt(merged))
|
|
905
919
|
if (rpc.success) data.push(humanizeReceipt(rpc.data))
|
|
906
920
|
}
|
|
@@ -950,8 +964,7 @@ function countFilters(options: countReceipts.Options): string[] {
|
|
|
950
964
|
if (options.sender !== undefined) filters.push(`"from" = '${options.sender}'`)
|
|
951
965
|
if (options.recipient !== undefined) filters.push(`"to" = '${options.recipient}'`)
|
|
952
966
|
if (options.feePayer !== undefined) filters.push(`fee_payer = '${options.feePayer}'`)
|
|
953
|
-
if (options.feeToken !== undefined)
|
|
954
|
-
filters.push(`tx_hash IN (SELECT hash FROM txs WHERE fee_token = '${options.feeToken}')`)
|
|
967
|
+
if (options.feeToken !== undefined) filters.push(`fee_token = '${options.feeToken}'`)
|
|
955
968
|
if (options.fromBlock !== undefined) filters.push(`block_num >= ${options.fromBlock}`)
|
|
956
969
|
if (options.toBlock !== undefined) filters.push(`block_num <= ${options.toBlock}`)
|
|
957
970
|
if (options.fromTimestamp !== undefined)
|
|
@@ -1003,7 +1016,11 @@ export function countReceipts(
|
|
|
1003
1016
|
) AS capped
|
|
1004
1017
|
`
|
|
1005
1018
|
}
|
|
1006
|
-
const result = await tidx.fetch({
|
|
1019
|
+
const result = await tidx.fetch({
|
|
1020
|
+
chainId,
|
|
1021
|
+
...(options.feeToken !== undefined ? { engine: 'clickhouse' } : {}),
|
|
1022
|
+
query: query as string,
|
|
1023
|
+
})
|
|
1007
1024
|
const totalCount = Value.toNumber(result.rows[0]?.['total']) ?? 0
|
|
1008
1025
|
return { totalCountCapped: totalCount >= Schema.countCap, totalCount }
|
|
1009
1026
|
},
|
|
@@ -1121,6 +1121,57 @@ describe('GET /tokens/:token/transactions', () => {
|
|
|
1121
1121
|
expect(secondBody.data.every((entry) => !firstHashes.has(entry.hash))).toBe(true)
|
|
1122
1122
|
})
|
|
1123
1123
|
|
|
1124
|
+
test('filters and counts by fee payer through receipts with pagination', async () => {
|
|
1125
|
+
const client = app()
|
|
1126
|
+
const seedResponse = await client.v1.tokens[':token'].transactions.$get(
|
|
1127
|
+
{ param: { token: TestApp.token }, query: { limit: '50' } },
|
|
1128
|
+
TestApp.auth,
|
|
1129
|
+
)
|
|
1130
|
+
const seed = await TestApp.json(seedResponse, Tokens.schema.getTokenTransactions.Response)
|
|
1131
|
+
const feePayers = seed.data.flatMap((transaction) => {
|
|
1132
|
+
const feePayer = transaction.meta.rpc.feePayer
|
|
1133
|
+
return typeof feePayer === 'string' ? [feePayer] : []
|
|
1134
|
+
})
|
|
1135
|
+
const feePayer = feePayers.find(
|
|
1136
|
+
(candidate) => feePayers.filter((value) => value === candidate).length > 5,
|
|
1137
|
+
)
|
|
1138
|
+
if (!feePayer) return
|
|
1139
|
+
|
|
1140
|
+
const first = await client.v1.tokens[':token'].transactions.$get(
|
|
1141
|
+
{
|
|
1142
|
+
param: { token: TestApp.token },
|
|
1143
|
+
query: { feePayer, include: 'totalCount', limit: '5' },
|
|
1144
|
+
},
|
|
1145
|
+
TestApp.auth,
|
|
1146
|
+
)
|
|
1147
|
+
const firstBody = await TestApp.json(first, Tokens.schema.getTokenTransactions.Response)
|
|
1148
|
+
expect(first.status).toBe(200)
|
|
1149
|
+
expect(first.headers.get('server-timing')).toContain('receipts;dur=')
|
|
1150
|
+
expect(first.headers.get('server-timing')).toContain('receipts_count;dur=')
|
|
1151
|
+
expect(firstBody.data).toHaveLength(5)
|
|
1152
|
+
expect(firstBody.meta?.totalCount).toBeGreaterThanOrEqual(firstBody.data.length)
|
|
1153
|
+
expect(firstBody.data.every((transaction) => transaction.meta.rpc.feePayer === feePayer)).toBe(
|
|
1154
|
+
true,
|
|
1155
|
+
)
|
|
1156
|
+
if (!firstBody.nextCursor) return
|
|
1157
|
+
|
|
1158
|
+
const second = await client.v1.tokens[':token'].transactions.$get(
|
|
1159
|
+
{
|
|
1160
|
+
param: { token: TestApp.token },
|
|
1161
|
+
query: { cursor: firstBody.nextCursor, feePayer, limit: '5' },
|
|
1162
|
+
},
|
|
1163
|
+
TestApp.auth,
|
|
1164
|
+
)
|
|
1165
|
+
const secondBody = await TestApp.json(second, Tokens.schema.getTokenTransactions.Response)
|
|
1166
|
+
expect(second.status).toBe(200)
|
|
1167
|
+
expect(secondBody.data.length).toBeGreaterThan(0)
|
|
1168
|
+
expect(secondBody.data.every((transaction) => transaction.meta.rpc.feePayer === feePayer)).toBe(
|
|
1169
|
+
true,
|
|
1170
|
+
)
|
|
1171
|
+
const firstHashes = new Set(firstBody.data.map((transaction) => transaction.hash))
|
|
1172
|
+
expect(secondBody.data.every((transaction) => !firstHashes.has(transaction.hash))).toBe(true)
|
|
1173
|
+
})
|
|
1174
|
+
|
|
1124
1175
|
test('returns 400 for an invalid token address', async () => {
|
|
1125
1176
|
// The typed client requires `token: `Hex.Hex`` so an invalid literal
|
|
1126
1177
|
// cannot pass through it; exercise the validator via `app.request`.
|
|
@@ -311,6 +311,48 @@ describe('GET /transactions', () => {
|
|
|
311
311
|
`)
|
|
312
312
|
})
|
|
313
313
|
|
|
314
|
+
test('filters and counts by fee payer through receipts with pagination', async () => {
|
|
315
|
+
const client = TestApp.client()
|
|
316
|
+
const seedResponse = await client.v1.transactions.$get({ query: { limit: '50' } }, TestApp.auth)
|
|
317
|
+
const seed = await TestApp.json(seedResponse, Transactions.schema.getTransactions.Response)
|
|
318
|
+
const feePayers = seed.data.flatMap((transaction) => {
|
|
319
|
+
const feePayer = transaction.meta.rpc.feePayer
|
|
320
|
+
return typeof feePayer === 'string' ? [feePayer] : []
|
|
321
|
+
})
|
|
322
|
+
const feePayer = feePayers.find(
|
|
323
|
+
(candidate) => feePayers.filter((value) => value === candidate).length > 5,
|
|
324
|
+
)
|
|
325
|
+
if (!feePayer) return
|
|
326
|
+
|
|
327
|
+
const first = await client.v1.transactions.$get(
|
|
328
|
+
{ query: { feePayer, include: 'totalCount', limit: '5' } },
|
|
329
|
+
TestApp.auth,
|
|
330
|
+
)
|
|
331
|
+
const firstBody = await TestApp.json(first, Transactions.schema.getTransactions.Response)
|
|
332
|
+
expect(first.status).toBe(200)
|
|
333
|
+
expect(first.headers.get('server-timing')).toContain('receipts;dur=')
|
|
334
|
+
expect(first.headers.get('server-timing')).toContain('receipts_count;dur=')
|
|
335
|
+
expect(firstBody.data).toHaveLength(5)
|
|
336
|
+
expect(firstBody.meta?.totalCount).toBeGreaterThanOrEqual(firstBody.data.length)
|
|
337
|
+
expect(firstBody.data.every((transaction) => transaction.meta.rpc.feePayer === feePayer)).toBe(
|
|
338
|
+
true,
|
|
339
|
+
)
|
|
340
|
+
if (!firstBody.nextCursor) return
|
|
341
|
+
|
|
342
|
+
const second = await client.v1.transactions.$get(
|
|
343
|
+
{ query: { cursor: firstBody.nextCursor, feePayer, limit: '5' } },
|
|
344
|
+
TestApp.auth,
|
|
345
|
+
)
|
|
346
|
+
const secondBody = await TestApp.json(second, Transactions.schema.getTransactions.Response)
|
|
347
|
+
expect(second.status).toBe(200)
|
|
348
|
+
expect(secondBody.data.length).toBeGreaterThan(0)
|
|
349
|
+
expect(secondBody.data.every((transaction) => transaction.meta.rpc.feePayer === feePayer)).toBe(
|
|
350
|
+
true,
|
|
351
|
+
)
|
|
352
|
+
const firstHashes = new Set(firstBody.data.map((transaction) => transaction.hash))
|
|
353
|
+
expect(secondBody.data.every((transaction) => !firstHashes.has(transaction.hash))).toBe(true)
|
|
354
|
+
})
|
|
355
|
+
|
|
314
356
|
test('filters reverted transactions by execution status', async () => {
|
|
315
357
|
const client = TestApp.client()
|
|
316
358
|
const response = await client.v1.transactions.$get(
|
|
@@ -186,6 +186,12 @@ export namespace schema {
|
|
|
186
186
|
'The chain ID that the transaction is valid on; legacy transactions may omit it.',
|
|
187
187
|
),
|
|
188
188
|
),
|
|
189
|
+
feePayer: z
|
|
190
|
+
.optional(Schema.Address)
|
|
191
|
+
.check(
|
|
192
|
+
z.describe('The address that paid the transaction fee.'),
|
|
193
|
+
z.meta({ examples: ['0xbe058e1c4df8a4366a387bf595b284246a93039e'] }),
|
|
194
|
+
),
|
|
189
195
|
feePayerSignature: z
|
|
190
196
|
.nullish(SignatureEnvelope)
|
|
191
197
|
.check(
|
|
@@ -811,8 +817,8 @@ export function transactions() {
|
|
|
811
817
|
|
|
812
818
|
/**
|
|
813
819
|
* Lists humanized transactions with required fee-token metadata and opt-in
|
|
814
|
-
* resources. Status filters page through receipts because
|
|
815
|
-
*
|
|
820
|
+
* resources. Status and fee-payer filters page through receipts because those
|
|
821
|
+
* fields are indexed there.
|
|
816
822
|
*/
|
|
817
823
|
export async function listTransactions(
|
|
818
824
|
c: Context<App.Environment>,
|
|
@@ -820,31 +826,28 @@ export async function listTransactions(
|
|
|
820
826
|
) {
|
|
821
827
|
const { include, ...rest } = options
|
|
822
828
|
|
|
823
|
-
// `status`
|
|
824
|
-
//
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
toBlock: rest.toBlock,
|
|
846
|
-
toTimestamp: rest.toTimestamp,
|
|
847
|
-
})
|
|
829
|
+
// `status` lives on receipts, whose fee-payer index also avoids an unbounded
|
|
830
|
+
// `txs.fee_payer` scan. Its cursor matches the transaction cursor domain.
|
|
831
|
+
const receiptDriven = rest.status !== undefined || rest.feePayer !== undefined
|
|
832
|
+
const receiptPage = receiptDriven
|
|
833
|
+
? await Receipts.getReceipts(c, {
|
|
834
|
+
address: rest.address,
|
|
835
|
+
chainId: rest.chainId,
|
|
836
|
+
cursor: rest.cursor,
|
|
837
|
+
feePayer: rest.feePayer,
|
|
838
|
+
feeToken: rest.feeToken,
|
|
839
|
+
fromBlock: rest.fromBlock,
|
|
840
|
+
fromTimestamp: rest.fromTimestamp,
|
|
841
|
+
limit: rest.limit,
|
|
842
|
+
order: rest.order,
|
|
843
|
+
page: rest.page,
|
|
844
|
+
recipient: rest.recipient,
|
|
845
|
+
sender: rest.sender,
|
|
846
|
+
status: rest.status,
|
|
847
|
+
toBlock: rest.toBlock,
|
|
848
|
+
toTimestamp: rest.toTimestamp,
|
|
849
|
+
})
|
|
850
|
+
: undefined
|
|
848
851
|
const page = receiptPage
|
|
849
852
|
? {
|
|
850
853
|
data: await transactionsForReceipts(c, {
|
|
@@ -885,7 +888,7 @@ export declare namespace listTransactions {
|
|
|
885
888
|
* Fetches the underlying transactions for a page of receipts in one indexed
|
|
886
889
|
* `txs` query and returns them humanized, ordered to match the receipt page.
|
|
887
890
|
* Receipts whose transaction row is missing (not yet indexed) are skipped.
|
|
888
|
-
* Backs
|
|
891
|
+
* Backs status- and fee-payer-filtered lists, which page receipts first.
|
|
889
892
|
*/
|
|
890
893
|
async function transactionsForReceipts(
|
|
891
894
|
c: Context<App.Environment>,
|
|
@@ -1275,20 +1278,21 @@ function countFilters(options: countTransactions.Options): string[] {
|
|
|
1275
1278
|
/**
|
|
1276
1279
|
* Capped total count of transactions matching the same filters as the page.
|
|
1277
1280
|
*
|
|
1278
|
-
*
|
|
1279
|
-
* the page's receipts-driven
|
|
1280
|
-
*
|
|
1281
|
-
*
|
|
1282
|
-
*
|
|
1283
|
-
*
|
|
1281
|
+
* Status and fee-payer filters are delegated to {@link Receipts.countReceipts},
|
|
1282
|
+
* mirroring the page's receipts-driven path. The address scope reuses the
|
|
1283
|
+
* page's `UNION` of the two sides; each side hits its own (`from`/`to`) index,
|
|
1284
|
+
* and the deduped keyset count avoids double-counting self-transactions
|
|
1285
|
+
* (`from = to`). `totalCountCapped: true` is a lower bound — at least
|
|
1286
|
+
* {@link Schema.countCap} matches.
|
|
1284
1287
|
*/
|
|
1285
1288
|
export function countTransactions(
|
|
1286
1289
|
c: Context<App.Environment>,
|
|
1287
1290
|
options: countTransactions.Options,
|
|
1288
1291
|
): Promise<z.output<typeof Schema.CountMeta>> {
|
|
1289
|
-
//
|
|
1290
|
-
//
|
|
1291
|
-
if (options.status !== undefined
|
|
1292
|
+
// Match the receipt-driven page path and avoid scanning unindexed
|
|
1293
|
+
// `txs.fee_payer` values when counting.
|
|
1294
|
+
if (options.status !== undefined || options.feePayer !== undefined)
|
|
1295
|
+
return Receipts.countReceipts(c, options)
|
|
1292
1296
|
|
|
1293
1297
|
const { address, chainId } = options
|
|
1294
1298
|
const store = c.get('store')
|
|
@@ -1373,8 +1377,8 @@ export declare namespace scanTransactions {
|
|
|
1373
1377
|
|
|
1374
1378
|
/**
|
|
1375
1379
|
* The `txs` columns {@link toRpcTransaction} reconstructs a transaction from.
|
|
1376
|
-
* Shared by the list scan and
|
|
1377
|
-
*
|
|
1380
|
+
* Shared by the list scan and receipt-driven paths so the queries cannot drift
|
|
1381
|
+
* from the reconstruction.
|
|
1378
1382
|
*/
|
|
1379
1383
|
const transactionColumns = `block_num, block_timestamp, idx, hash, type, "from", "to", value, input,
|
|
1380
1384
|
gas_limit, max_fee_per_gas, max_priority_fee_per_gas,
|
|
@@ -1396,6 +1400,7 @@ function toRpcTransaction(row: Record<string, unknown>) {
|
|
|
1396
1400
|
blockHash: null,
|
|
1397
1401
|
blockNumber: Value.toHex(row['block_num']) ?? null,
|
|
1398
1402
|
blockTimestamp: timestamp ? Value.toHex(Math.floor(Date.parse(timestamp) / 1000)) : undefined,
|
|
1403
|
+
feePayer: typeof row['fee_payer'] === 'string' ? row['fee_payer'] : undefined,
|
|
1399
1404
|
feeToken: typeof row['fee_token'] === 'string' ? row['fee_token'] : undefined,
|
|
1400
1405
|
from: row['from'],
|
|
1401
1406
|
gas: Value.toHex(row['gas_limit']),
|