tapimo 0.10.1 → 0.10.2
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 +1776 -1543
- package/dist/App.d.ts.map +1 -1
- package/dist/Webhooks.d.ts +1 -1
- package/dist/Webhooks.d.ts.map +1 -1
- package/dist/Webhooks.js +1 -1
- package/dist/Webhooks.js.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +4 -4
- package/dist/analytics/Analytics.d.ts.map +1 -1
- package/dist/analytics/Analytics.js +206 -0
- package/dist/analytics/Analytics.js.map +1 -1
- package/dist/analytics/tables/requestEvents.d.ts +4 -0
- package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
- package/dist/analytics/tables/requestEvents.js +2 -0
- package/dist/analytics/tables/requestEvents.js.map +1 -1
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +2 -0
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/routes/addresses.d.ts +340 -340
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +2 -6
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/coingecko.d.ts +176 -176
- package/dist/apps/data/routes/earn.d.ts.map +1 -1
- package/dist/apps/data/routes/earn.js +63 -34
- package/dist/apps/data/routes/earn.js.map +1 -1
- package/dist/apps/data/routes/exchanges.d.ts +4 -0
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
- package/dist/apps/data/routes/exchanges.js +115 -77
- package/dist/apps/data/routes/exchanges.js.map +1 -1
- package/dist/apps/data/routes/indexer.d.ts +34 -34
- package/dist/apps/data/routes/receipts.d.ts +4 -3
- package/dist/apps/data/routes/receipts.d.ts.map +1 -1
- package/dist/apps/data/routes/receipts.js +15 -14
- package/dist/apps/data/routes/receipts.js.map +1 -1
- package/dist/apps/data/routes/tokens.d.ts +1 -1
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/tokens.js +4 -3
- package/dist/apps/data/routes/tokens.js.map +1 -1
- package/dist/apps/data/routes/transactions.d.ts +281 -281
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +25 -7
- package/dist/apps/data/routes/transfers.js.map +1 -1
- package/dist/apps/data/routes/webhooks.d.ts +380 -364
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +16 -9
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/data/routes/zones.d.ts +31 -31
- package/dist/apps/funding/App.d.ts +233 -0
- package/dist/apps/funding/App.d.ts.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.d.ts +330 -0
- package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.js +207 -11
- package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
- package/dist/apps/funding/routes/providers.d.ts +13 -13
- package/dist/apps/management/routes/faucet.d.ts +32 -32
- package/dist/apps/management/routes/members.d.ts +79 -79
- package/dist/apps/management/routes/usage.d.ts +80 -80
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +32 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/tables/fundingDepositAddresses.d.ts +20 -0
- package/dist/db/tables/fundingDepositAddresses.d.ts.map +1 -1
- package/dist/db/tables/fundingDepositAddresses.js +17 -0
- package/dist/db/tables/fundingDepositAddresses.js.map +1 -1
- package/dist/db/tables/fundingDeposits.d.ts +41 -0
- package/dist/db/tables/fundingDeposits.d.ts.map +1 -1
- package/dist/db/tables/fundingDeposits.js +69 -0
- package/dist/db/tables/fundingDeposits.js.map +1 -1
- package/dist/internal/Auth.d.ts +0 -26
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +0 -33
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/Credential.d.ts +27 -0
- package/dist/internal/Credential.d.ts.map +1 -0
- package/dist/internal/Credential.js +34 -0
- package/dist/internal/Credential.js.map +1 -0
- package/dist/internal/EarnRates.js +11 -7
- package/dist/internal/EarnRates.js.map +1 -1
- package/dist/internal/Log.d.ts +8 -0
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +4 -0
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/MetricSink.d.ts +24 -0
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +25 -0
- package/dist/internal/MetricSink.js.map +1 -1
- package/dist/internal/Tidx.d.ts +4 -3
- package/dist/internal/Tidx.d.ts.map +1 -1
- package/dist/internal/Tidx.js +2 -2
- package/dist/internal/Tidx.js.map +1 -1
- package/dist/internal/Viem.d.ts +4 -3
- package/dist/internal/Viem.d.ts.map +1 -1
- package/dist/internal/Viem.js +3 -2
- package/dist/internal/Viem.js.map +1 -1
- package/dist/internal/Webhooks.d.ts +2 -0
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +10 -3
- package/dist/internal/Webhooks.js.map +1 -1
- package/dist/internal/index.d.ts +1 -0
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +1 -0
- package/dist/internal/index.js.map +1 -1
- package/package.json +1 -1
- package/src/App.test.ts +30 -0
- package/src/Client.test-d.ts +33 -1
- package/src/Webhooks.ts +1 -1
- package/src/analytics/Analytics.test.ts +34 -0
- package/src/analytics/Analytics.ts +206 -0
- package/src/analytics/tables/requestEvents.ts +6 -0
- package/src/apps/composition.test.ts +1 -0
- package/src/apps/data/App.ts +2 -0
- package/src/apps/data/routes/balances.ts +2 -6
- package/src/apps/data/routes/earn.ts +64 -34
- package/src/apps/data/routes/exchanges.test.ts +8 -3
- package/src/apps/data/routes/exchanges.ts +134 -82
- package/src/apps/data/routes/receipts.ts +16 -14
- package/src/apps/data/routes/tokens.ts +3 -2
- package/src/apps/data/routes/transactions.bench.ts +8 -0
- package/src/apps/data/routes/transfers.ts +36 -10
- package/src/apps/data/routes/webhooks.ts +31 -10
- package/src/apps/funding/routes/deposit-addresses.test.ts +216 -2
- package/src/apps/funding/routes/deposit-addresses.ts +254 -18
- package/src/apps/funding/routes/transfers.test.ts +17 -2
- package/src/db/Db.ts +36 -0
- package/src/db/tables/fundingDepositAddresses.test.ts +28 -0
- package/src/db/tables/fundingDepositAddresses.ts +39 -0
- package/src/db/tables/fundingDeposits.test.ts +82 -0
- package/src/db/tables/fundingDeposits.ts +130 -0
- package/src/internal/Auth.test.ts +0 -48
- package/src/internal/Auth.ts +0 -59
- package/src/internal/Credential.test.ts +51 -0
- package/src/internal/Credential.ts +58 -0
- package/src/internal/EarnRates.ts +13 -7
- package/src/internal/Log.test.ts +3 -1
- package/src/internal/Log.ts +14 -0
- package/src/internal/MetricSink.test.ts +50 -1
- package/src/internal/MetricSink.ts +52 -0
- package/src/internal/Tidx.ts +5 -4
- package/src/internal/Viem.test.ts +4 -0
- package/src/internal/Viem.ts +6 -4
- package/src/internal/Webhooks.test.ts +9 -0
- package/src/internal/Webhooks.ts +10 -3
- package/src/internal/index.test.ts +1 -0
- package/src/internal/index.ts +1 -0
|
@@ -375,9 +375,7 @@ export function addresses(options: addresses.Options = {}) {
|
|
|
375
375
|
),
|
|
376
376
|
)
|
|
377
377
|
const client = c.get('getClient')(chainId)
|
|
378
|
-
const caller = c.get('zones').has(chainId)
|
|
379
|
-
? (await client.zone.getZoneInfo()).sequencers[0]
|
|
380
|
-
: undefined
|
|
378
|
+
const caller = c.get('zones').has(chainId) ? address : undefined
|
|
381
379
|
const [balance, feeTokens, snapshot, resolvedToken] = await Promise.all([
|
|
382
380
|
Timing.time(c, 'balance_rpc', async () => {
|
|
383
381
|
const call = Actions.token.getBalance.call(client, {
|
|
@@ -516,9 +514,7 @@ async function getAddressBalances(
|
|
|
516
514
|
const correctedPromise = holdingsPromise.then(async (holdings) => {
|
|
517
515
|
if (holdings.length === 0) return []
|
|
518
516
|
const client = c.get('getClient')(chainId)
|
|
519
|
-
const caller = c.get('zones').has(chainId)
|
|
520
|
-
? (await client.zone.getZoneInfo()).sequencers[0]
|
|
521
|
-
: undefined
|
|
517
|
+
const caller = c.get('zones').has(chainId) ? address : undefined
|
|
522
518
|
return Timing.time(c, 'balances_rpc', async () => {
|
|
523
519
|
// Concurrent reads enter viem's shared batch window and become one
|
|
524
520
|
// deployless multicall. A failed read fails the request rather than serving
|
|
@@ -2034,6 +2034,8 @@ export async function listVaults(c: Context<App.Environment>, options: listVault
|
|
|
2034
2034
|
core_EarnVaults.list(db, { chainId: options.chainId }),
|
|
2035
2035
|
VerifiedTokens.snapshot(c, options.chainId),
|
|
2036
2036
|
])
|
|
2037
|
+
const include = vaultInclude(options)
|
|
2038
|
+
const apyByVault = new Map<string, EarnRates.Apy | null>()
|
|
2037
2039
|
const registry = new Map(records.map((record) => [record.vaultAddress.toLowerCase(), record]))
|
|
2038
2040
|
const decoded = options.cursor ? Cursor.decode(options.cursor, ['int', 'int']) : undefined
|
|
2039
2041
|
let position = decoded
|
|
@@ -2062,22 +2064,44 @@ export async function listVaults(c: Context<App.Environment>, options: listVault
|
|
|
2062
2064
|
}
|
|
2063
2065
|
scanned += page.items.length
|
|
2064
2066
|
|
|
2065
|
-
const
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2067
|
+
const apyVaults =
|
|
2068
|
+
apyByVault.size === 0 &&
|
|
2069
|
+
include.includes('apy') &&
|
|
2070
|
+
options.capability.length === 0 &&
|
|
2071
|
+
options['engine.type'] === undefined
|
|
2072
|
+
? page.items
|
|
2073
|
+
.slice(0, options.limit - matches.length)
|
|
2074
|
+
.map(({ vaultAddress }) => vaultAddress)
|
|
2075
|
+
: []
|
|
2076
|
+
// Rate boundaries and historical quotes depend only on candidate addresses,
|
|
2077
|
+
// so the unfiltered page can resolve them alongside live vault discovery.
|
|
2078
|
+
const [discovered, prefetchedApy] = await Promise.all([
|
|
2079
|
+
Promise.all(
|
|
2080
|
+
page.items.map(async (item) => {
|
|
2081
|
+
const record = registry.get(item.vaultAddress.toLowerCase())
|
|
2082
|
+
try {
|
|
2083
|
+
const discovery = await resolveVaultDiscovery(c, {
|
|
2084
|
+
chainId: options.chainId,
|
|
2085
|
+
include: discoveryInclude(options),
|
|
2086
|
+
vaultAddress: item.vaultAddress,
|
|
2087
|
+
})
|
|
2088
|
+
return { discovery, position: item.position, record }
|
|
2089
|
+
} catch (cause) {
|
|
2090
|
+
if (!record && isIncompatible(cause)) return undefined
|
|
2091
|
+
throw cause
|
|
2092
|
+
}
|
|
2093
|
+
}),
|
|
2094
|
+
),
|
|
2095
|
+
apyVaults.length > 0
|
|
2096
|
+
? resolvePageApy(c, {
|
|
2070
2097
|
chainId: options.chainId,
|
|
2071
|
-
include
|
|
2072
|
-
|
|
2098
|
+
include,
|
|
2099
|
+
vaults: apyVaults,
|
|
2100
|
+
window: options['apy.window'],
|
|
2073
2101
|
})
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
throw cause
|
|
2078
|
-
}
|
|
2079
|
-
}),
|
|
2080
|
-
)
|
|
2102
|
+
: undefined,
|
|
2103
|
+
])
|
|
2104
|
+
for (const [vault, apy] of prefetchedApy ?? []) apyByVault.set(vault, apy)
|
|
2081
2105
|
for (const match of discovered) {
|
|
2082
2106
|
if (!match || !matchesVaultFilters(match.discovery, options)) continue
|
|
2083
2107
|
matches.push(match)
|
|
@@ -2089,17 +2113,25 @@ export async function listVaults(c: Context<App.Environment>, options: listVault
|
|
|
2089
2113
|
|
|
2090
2114
|
const page = matches.slice(0, options.limit)
|
|
2091
2115
|
const discoveries = page.map(({ discovery }) => discovery)
|
|
2092
|
-
const
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2116
|
+
const missingApyVaults = include.includes('apy')
|
|
2117
|
+
? discoveries.flatMap(({ vaultAddress }) =>
|
|
2118
|
+
apyByVault.has(vaultAddress.toLowerCase()) ? [] : [vaultAddress],
|
|
2119
|
+
)
|
|
2120
|
+
: []
|
|
2121
|
+
const [missingApy, rates, tokens] = await Promise.all([
|
|
2122
|
+
missingApyVaults.length > 0
|
|
2123
|
+
? resolvePageApy(c, {
|
|
2124
|
+
chainId: options.chainId,
|
|
2125
|
+
include,
|
|
2126
|
+
vaults: missingApyVaults,
|
|
2127
|
+
window: options['apy.window'],
|
|
2128
|
+
})
|
|
2129
|
+
: undefined,
|
|
2100
2130
|
resolvePageRates(c, { discoveries, include, oracle: options.oracle, snapshot }),
|
|
2101
2131
|
resolvePageTokens(c, { chainId: options.chainId, discoveries, include, snapshot }),
|
|
2102
2132
|
])
|
|
2133
|
+
for (const [vault, apy] of missingApy ?? []) apyByVault.set(vault, apy)
|
|
2134
|
+
const apy = include.includes('apy') ? apyByVault : undefined
|
|
2103
2135
|
const data = page.map((match) =>
|
|
2104
2136
|
resolveVault(c, { ...match, apy, include, rates, snapshot, tokens }),
|
|
2105
2137
|
)
|
|
@@ -2181,8 +2213,8 @@ export async function listVerifiedVaults(
|
|
|
2181
2213
|
const [apy, rates, tokens] = await Promise.all([
|
|
2182
2214
|
resolvePageApy(c, {
|
|
2183
2215
|
chainId: options.chainId,
|
|
2184
|
-
discoveries,
|
|
2185
2216
|
include,
|
|
2217
|
+
vaults: discoveries.map(({ vaultAddress }) => vaultAddress),
|
|
2186
2218
|
window: options['apy.window'],
|
|
2187
2219
|
}),
|
|
2188
2220
|
resolvePageRates(c, { discoveries, include, oracle: options.oracle, snapshot }),
|
|
@@ -2503,8 +2535,8 @@ export async function getVault(c: Context<App.Environment>, options: getVault.Op
|
|
|
2503
2535
|
const [apy, rates, tokens] = await Promise.all([
|
|
2504
2536
|
resolvePageApy(c, {
|
|
2505
2537
|
chainId: options.chainId,
|
|
2506
|
-
discoveries,
|
|
2507
2538
|
include,
|
|
2539
|
+
vaults: discoveries.map(({ vaultAddress }) => vaultAddress),
|
|
2508
2540
|
window: options['apy.window'],
|
|
2509
2541
|
}),
|
|
2510
2542
|
resolvePageRates(c, { discoveries, include, oracle: options.oracle, snapshot }),
|
|
@@ -2616,7 +2648,7 @@ function resolvePageApy(c: Context<App.Environment>, options: resolvePageApy.Opt
|
|
|
2616
2648
|
getClient: c.get('getClient'),
|
|
2617
2649
|
getTidx: c.get('getTidx'),
|
|
2618
2650
|
store: c.get('store'),
|
|
2619
|
-
vaults: options.
|
|
2651
|
+
vaults: options.vaults,
|
|
2620
2652
|
window: options.window,
|
|
2621
2653
|
}),
|
|
2622
2654
|
)
|
|
@@ -2625,8 +2657,8 @@ function resolvePageApy(c: Context<App.Environment>, options: resolvePageApy.Opt
|
|
|
2625
2657
|
declare namespace resolvePageApy {
|
|
2626
2658
|
type Options = {
|
|
2627
2659
|
chainId: z.output<typeof Schema.ChainId>
|
|
2628
|
-
discoveries: readonly EarnVaults.Discovery[]
|
|
2629
2660
|
include: z.output<typeof schema.getEarnVault.Query>['include']
|
|
2661
|
+
vaults: readonly z.output<typeof Schema.Address>[]
|
|
2630
2662
|
window: z.output<typeof schema.getEarnVault.Query>['apy.window']
|
|
2631
2663
|
}
|
|
2632
2664
|
}
|
|
@@ -2837,7 +2869,7 @@ async function queryDeployments(
|
|
|
2837
2869
|
c: Context<App.Environment>,
|
|
2838
2870
|
options: queryDeployments.Options,
|
|
2839
2871
|
): Promise<queryDeployments.Result> {
|
|
2840
|
-
const filters
|
|
2872
|
+
const filters: string[] = []
|
|
2841
2873
|
if (options.asset) filters.push(`lower(asset) = '${options.asset.toLowerCase()}'`)
|
|
2842
2874
|
if (options.cursor)
|
|
2843
2875
|
filters.push(
|
|
@@ -2855,7 +2887,7 @@ async function queryDeployments(
|
|
|
2855
2887
|
]),
|
|
2856
2888
|
)
|
|
2857
2889
|
|
|
2858
|
-
//
|
|
2890
|
+
// Deduplicate before outer filters so every vault is filtered by its latest deployment.
|
|
2859
2891
|
const result = await c
|
|
2860
2892
|
.get('getTidx')(options.chainId)
|
|
2861
2893
|
.fetch({
|
|
@@ -2868,14 +2900,12 @@ async function queryDeployments(
|
|
|
2868
2900
|
"earnVault",
|
|
2869
2901
|
asset,
|
|
2870
2902
|
block_num,
|
|
2871
|
-
log_idx
|
|
2872
|
-
row_number() OVER (
|
|
2873
|
-
PARTITION BY lower("earnVault")
|
|
2874
|
-
ORDER BY block_num DESC, log_idx DESC
|
|
2875
|
-
) AS deployment_rank
|
|
2903
|
+
log_idx
|
|
2876
2904
|
FROM EarnStackDeployed
|
|
2905
|
+
ORDER BY block_num DESC, log_idx DESC
|
|
2906
|
+
LIMIT 1 BY lower("earnVault")
|
|
2877
2907
|
)
|
|
2878
|
-
WHERE ${filters.join(' AND ')}
|
|
2908
|
+
${filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : ''}
|
|
2879
2909
|
ORDER BY block_num DESC, log_idx DESC
|
|
2880
2910
|
LIMIT ${options.limit + 1}
|
|
2881
2911
|
` as string,
|
|
@@ -531,7 +531,7 @@ describe('GET /exchange/orders?base=… (pair-scoped orders)', () => {
|
|
|
531
531
|
})
|
|
532
532
|
|
|
533
533
|
describe('GET /exchange/pairs/:base/depth', () => {
|
|
534
|
-
test('returns orderbook depth
|
|
534
|
+
test('returns orderbook depth when token metadata is requested', async () => {
|
|
535
535
|
const client = TestApp.client()
|
|
536
536
|
const pairsResponse = await client.v1.exchange.pairs.$get(
|
|
537
537
|
{ query: { limit: '5' } },
|
|
@@ -547,21 +547,25 @@ describe('GET /exchange/pairs/:base/depth', () => {
|
|
|
547
547
|
// throttles the multicall response.
|
|
548
548
|
let body: z.output<typeof Exchanges.schema.getPairDepth.Response> | null = null
|
|
549
549
|
let pair: (typeof pairs.data)[number] | null = null
|
|
550
|
+
let timing: string | null = null
|
|
550
551
|
for (const candidate of pairs.data) {
|
|
551
552
|
const response = await client.v1.exchange.pairs[':base'].depth.$get(
|
|
552
|
-
{ param: { base: candidate.base.address }, query: {} },
|
|
553
|
+
{ param: { base: candidate.base.address }, query: { include: 'tokens' } },
|
|
553
554
|
TestApp.auth,
|
|
554
555
|
)
|
|
555
556
|
if (response.status === 502) return
|
|
556
557
|
expect(response.status).toBe(200)
|
|
557
558
|
body = await TestApp.json(response, Exchanges.schema.getPairDepth.Response)
|
|
558
559
|
pair = candidate
|
|
560
|
+
timing = response.headers.get('server-timing')
|
|
559
561
|
if (body.bids.length > 0 || body.asks.length > 0) break
|
|
560
562
|
}
|
|
561
563
|
if (!body || !pair) return
|
|
562
564
|
|
|
563
565
|
expect(body.base.address).toBe(pair.base.address)
|
|
564
566
|
expect(body.quote.address).toBe(pair.quote.address)
|
|
567
|
+
expect(timing).toContain('pair_lookup;dur=')
|
|
568
|
+
expect(timing).not.toContain('pair_index;dur=')
|
|
565
569
|
|
|
566
570
|
// Asks are emitted lowest-tick first (best ask outward); bids are
|
|
567
571
|
// emitted highest-tick first (best bid outward). Per-row `cumulativeSize`
|
|
@@ -689,9 +693,9 @@ describe('GET /exchange/swaps', () => {
|
|
|
689
693
|
const response = await client.v1.exchange.swaps.$get({ query: { limit: '10' } }, TestApp.auth)
|
|
690
694
|
expect(response.status).toBe(200)
|
|
691
695
|
const body = await TestApp.json(response, Exchanges.schema.getSwaps.Response)
|
|
696
|
+
expect(body.data.length).toBeGreaterThan(0)
|
|
692
697
|
expect(body.data.length).toBeLessThanOrEqual(10)
|
|
693
698
|
expect(response.headers.get('server-timing')).not.toContain('valuation_rates;dur=')
|
|
694
|
-
if (body.data.length === 0) return
|
|
695
699
|
|
|
696
700
|
for (const swap of body.data) {
|
|
697
701
|
expect(swap.taker).toMatch(/^0x[0-9a-f]{40}$/)
|
|
@@ -827,6 +831,7 @@ describe('GET /exchange/swaps', () => {
|
|
|
827
831
|
TestApp.auth,
|
|
828
832
|
)
|
|
829
833
|
expect(response.status).toBe(200)
|
|
834
|
+
expect(response.headers.get('server-timing')).not.toContain('token_logo_uri;dur=')
|
|
830
835
|
const body = await TestApp.json(response, Exchanges.schema.getSwaps.Response)
|
|
831
836
|
if (body.data.length === 0) return
|
|
832
837
|
|
|
@@ -2209,20 +2209,22 @@ export function exchanges(options: exchanges.Options = {}) {
|
|
|
2209
2209
|
transactionHash: options.transactionHash,
|
|
2210
2210
|
})
|
|
2211
2211
|
|
|
2212
|
-
// `route` spans every swap and fill side, so one lookup resolves each
|
|
2213
|
-
// token once for required RPC metadata and requested curated fields.
|
|
2214
|
-
const tokensByAddress = await Tokens.resolveTokens(c, {
|
|
2215
|
-
addresses: page.data.flatMap((swap) => swap.route),
|
|
2216
|
-
chainId,
|
|
2217
|
-
include: options.include,
|
|
2218
|
-
})
|
|
2219
2212
|
// Value amounts via their curated display currency; snapshot or rate
|
|
2220
2213
|
// failures degrade the values to `null`, never the feed.
|
|
2221
2214
|
const denomination = options['valuation.currency']
|
|
2222
|
-
|
|
2215
|
+
// Mode, verified-token, and RPC metadata reads depend only on the
|
|
2216
|
+
// assembled page, so overlap them before enriching its rows.
|
|
2217
|
+
const [, snapshot, tokensByAddress] = await Promise.all([
|
|
2218
|
+
resolveSwapModes(c, { chainId, swaps: page.data }),
|
|
2223
2219
|
denomination !== undefined && page.data.length > 0
|
|
2224
|
-
?
|
|
2225
|
-
: undefined
|
|
2220
|
+
? VerifiedTokens.snapshot(c, chainId).catch(() => undefined)
|
|
2221
|
+
: undefined,
|
|
2222
|
+
Tokens.resolveTokens(c, {
|
|
2223
|
+
addresses: page.data.flatMap((swap) => swap.route),
|
|
2224
|
+
chainId,
|
|
2225
|
+
include: options.include,
|
|
2226
|
+
}),
|
|
2227
|
+
])
|
|
2226
2228
|
const rates = denomination
|
|
2227
2229
|
? await Valuation.ratesFor(c, {
|
|
2228
2230
|
currencies: snapshot
|
|
@@ -3001,27 +3003,43 @@ export function exchanges(options: exchanges.Options = {}) {
|
|
|
3001
3003
|
const chainId = options.chainId ?? c.get('chainId')
|
|
3002
3004
|
|
|
3003
3005
|
try {
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3006
|
+
// Pair definitions are immutable after creation, so cache successful
|
|
3007
|
+
// point lookups without making unknown pairs stale.
|
|
3008
|
+
const target = await Store.memoize(
|
|
3009
|
+
async () =>
|
|
3010
|
+
(await getPairsByBase(c, { bases: [baseAddress], chainId })).get(baseAddress),
|
|
3011
|
+
{
|
|
3012
|
+
key: `exchange:v1:${chainId}:pair-by-base:${baseAddress}:v1`,
|
|
3013
|
+
store: c.get('store'),
|
|
3014
|
+
ttl: Ttl.minutes(5),
|
|
3015
|
+
},
|
|
3016
|
+
)
|
|
3017
|
+
if (!target)
|
|
3018
|
+
return pairNotFoundError(c, {
|
|
3019
|
+
base: baseAddress,
|
|
3020
|
+
pairIndex: await getPairIndex(c, { chainId }),
|
|
3021
|
+
})
|
|
3007
3022
|
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3023
|
+
// Token metadata is opt-in and resolves once per pair onto the top-level
|
|
3024
|
+
// `base`/`quote` slots. Run it alongside the independent depth RPC scan.
|
|
3025
|
+
const [{ asks, bids }, tokensByAddress] = await Promise.all([
|
|
3026
|
+
getPairDepth(c, {
|
|
3027
|
+
chainId,
|
|
3028
|
+
levels: options.levels,
|
|
3029
|
+
pair: target,
|
|
3030
|
+
}),
|
|
3031
|
+
options.include.includes('tokens')
|
|
3032
|
+
? Tokens.resolveTokens(c, {
|
|
3033
|
+
addresses: [target.base, target.quote],
|
|
3034
|
+
chainId,
|
|
3035
|
+
include: tokenFields,
|
|
3036
|
+
})
|
|
3037
|
+
: undefined,
|
|
3038
|
+
])
|
|
3013
3039
|
|
|
3014
|
-
// Token metadata is opt-in. Resolve once per pair and spread onto the
|
|
3015
|
-
// top-level `base`/`quote` PairToken slots. The Level rows carry no
|
|
3016
|
-
// token references so no per-row enrichment is needed.
|
|
3017
3040
|
let base: z.output<typeof schema.PairToken> = { address: target.base }
|
|
3018
3041
|
let quote: z.output<typeof schema.PairToken> = { address: target.quote }
|
|
3019
|
-
if (
|
|
3020
|
-
const tokensByAddress = await Tokens.resolveTokens(c, {
|
|
3021
|
-
addresses: [target.base, target.quote],
|
|
3022
|
-
chainId,
|
|
3023
|
-
include: tokenFields,
|
|
3024
|
-
})
|
|
3042
|
+
if (tokensByAddress) {
|
|
3025
3043
|
const baseMeta = tokensByAddress.get(target.base)
|
|
3026
3044
|
const quoteMeta = tokensByAddress.get(target.quote)
|
|
3027
3045
|
if (baseMeta) base = { ...base, ...baseMeta }
|
|
@@ -3536,9 +3554,9 @@ async function getSwaps(c: Context<App.Environment>, options: getSwaps.Options)
|
|
|
3536
3554
|
// the head page.
|
|
3537
3555
|
const cursor = cursorToken ? Cursor.decode(cursorToken, ['int', 'int', 'int']) : undefined
|
|
3538
3556
|
|
|
3539
|
-
//
|
|
3540
|
-
// `swapWindowBlocks`).
|
|
3541
|
-
type BlockWindow = { hi: number; lo: number }
|
|
3557
|
+
// `dex_fills` block window bounding one grouped batch (see
|
|
3558
|
+
// `swapWindowBlocks`); the head variant derives its lower bound in SQL.
|
|
3559
|
+
type BlockWindow = { hi: number; lo: number } | 'indexedHead'
|
|
3542
3560
|
|
|
3543
3561
|
// Row-level predicates (constant within a group) prune before aggregation.
|
|
3544
3562
|
// These are batch-invariant; the cursor's per-batch block bound is added in
|
|
@@ -3600,6 +3618,12 @@ async function getSwaps(c: Context<App.Environment>, options: getSwaps.Options)
|
|
|
3600
3618
|
// the group timestamp.
|
|
3601
3619
|
const fetchGroups = (activeCursor: typeof cursor, batchLimit: number, window: BlockWindow) => {
|
|
3602
3620
|
const { having, where } = withCursorSql(activeCursor)
|
|
3621
|
+
const windowFilters =
|
|
3622
|
+
window === 'indexedHead'
|
|
3623
|
+
? [
|
|
3624
|
+
`f.block_num >= greatest(toInt128((SELECT max(num) FROM blocks)) - ${swapWindowBlocks - 1}, toInt128(0))`,
|
|
3625
|
+
]
|
|
3626
|
+
: [`f.block_num >= ${window.lo}`, `f.block_num <= ${window.hi}`]
|
|
3603
3627
|
return tidx.fetch({
|
|
3604
3628
|
chainId,
|
|
3605
3629
|
engine: 'clickhouse',
|
|
@@ -3609,7 +3633,7 @@ async function getSwaps(c: Context<App.Environment>, options: getSwaps.Options)
|
|
|
3609
3633
|
groupArray(tuple(f.log_idx, toString(f."orderId"), f.maker,
|
|
3610
3634
|
toString(f."amountFilled"), f."partialFill")) AS fills
|
|
3611
3635
|
FROM dex_fills f
|
|
3612
|
-
WHERE ${[...where,
|
|
3636
|
+
WHERE ${[...where, ...windowFilters].join(' AND ')}
|
|
3613
3637
|
GROUP BY f.block_num, f.tx_hash, f.taker
|
|
3614
3638
|
${having}
|
|
3615
3639
|
ORDER BY block_num ${direction}, first_idx ${direction}
|
|
@@ -3667,19 +3691,16 @@ async function getSwaps(c: Context<App.Environment>, options: getSwaps.Options)
|
|
|
3667
3691
|
}),
|
|
3668
3692
|
)
|
|
3669
3693
|
|
|
3670
|
-
// Each fill's book quote
|
|
3671
|
-
//
|
|
3672
|
-
//
|
|
3673
|
-
// decode (genesis system books) and have no `dex_pairs` row.
|
|
3694
|
+
// Each fill's book quote comes back with its order state when indexed;
|
|
3695
|
+
// otherwise read the base token's on-chain `quoteToken()` — some books
|
|
3696
|
+
// predate the indexer's `PairCreated` decode (genesis system books).
|
|
3674
3697
|
const bases = [
|
|
3675
3698
|
...new Set([...states.values()].flatMap((events) => events.map((event) => event.token))),
|
|
3676
3699
|
]
|
|
3677
|
-
const pairs = await getPairsByBase(c, { bases, chainId })
|
|
3678
3700
|
const quoteByBase = new Map<string, z.output<typeof Schema.TokenAddress>>()
|
|
3679
|
-
for (const
|
|
3680
|
-
const
|
|
3681
|
-
|
|
3682
|
-
}
|
|
3701
|
+
for (const events of states.values())
|
|
3702
|
+
for (const event of events)
|
|
3703
|
+
if (event.quoteToken !== undefined) quoteByBase.set(event.token, event.quoteToken)
|
|
3683
3704
|
const fallback = await Timing.time(c, 'swap_quotes', () =>
|
|
3684
3705
|
resolveBookQuotes(c, {
|
|
3685
3706
|
bases: bases.filter((bookBase) => !quoteByBase.has(bookBase)),
|
|
@@ -3763,7 +3784,12 @@ async function getSwaps(c: Context<App.Environment>, options: getSwaps.Options)
|
|
|
3763
3784
|
return row ? Value.toNumber(row['block_num']) : undefined
|
|
3764
3785
|
}
|
|
3765
3786
|
|
|
3766
|
-
type ScanBounds =
|
|
3787
|
+
type ScanBounds = {
|
|
3788
|
+
firstMatchingBlock?: number | undefined
|
|
3789
|
+
firstWindow?: 'indexedHead' | undefined
|
|
3790
|
+
hi: number
|
|
3791
|
+
lo: number
|
|
3792
|
+
}
|
|
3767
3793
|
const bounds: ScanBounds | undefined = await Timing.time(c, 'swap_bounds', async () => {
|
|
3768
3794
|
if (selective) {
|
|
3769
3795
|
const firstMatchingBlock = await findMatchingBlock(cursorBlock)
|
|
@@ -3775,6 +3801,17 @@ async function getSwaps(c: Context<App.Environment>, options: getSwaps.Options)
|
|
|
3775
3801
|
}
|
|
3776
3802
|
: undefined
|
|
3777
3803
|
}
|
|
3804
|
+
// The newest unfiltered page can derive its 100k-block lower bound from
|
|
3805
|
+
// `blocks` inside the grouped query, avoiding a separate TIDX request.
|
|
3806
|
+
if (
|
|
3807
|
+
order === 'desc' &&
|
|
3808
|
+
cursorBlock === undefined &&
|
|
3809
|
+
fromBlock === undefined &&
|
|
3810
|
+
fromTimestamp === undefined &&
|
|
3811
|
+
toBlock === undefined &&
|
|
3812
|
+
toTimestamp === undefined
|
|
3813
|
+
)
|
|
3814
|
+
return { firstWindow: 'indexedHead', hi: Number.MAX_SAFE_INTEGER, lo: 0 }
|
|
3778
3815
|
const result = await tidx.fetch({
|
|
3779
3816
|
chainId,
|
|
3780
3817
|
engine: 'clickhouse',
|
|
@@ -3802,7 +3839,8 @@ async function getSwaps(c: Context<App.Environment>, options: getSwaps.Options)
|
|
|
3802
3839
|
let activeCursor = cursor
|
|
3803
3840
|
|
|
3804
3841
|
// Selective scans jump to the next populated block through a bounded
|
|
3805
|
-
// lookup. Unfiltered scans retain the fixed-stride min/max sweep
|
|
3842
|
+
// lookup. Unfiltered scans retain the fixed-stride min/max sweep after
|
|
3843
|
+
// the newest page's indexed-head window.
|
|
3806
3844
|
const windowFrom = async (
|
|
3807
3845
|
anchor: number | undefined,
|
|
3808
3846
|
knownMatchingBlock?: number,
|
|
@@ -3821,13 +3859,20 @@ async function getSwaps(c: Context<App.Environment>, options: getSwaps.Options)
|
|
|
3821
3859
|
: undefined
|
|
3822
3860
|
}
|
|
3823
3861
|
|
|
3824
|
-
let window
|
|
3862
|
+
let window: BlockWindow | undefined =
|
|
3863
|
+
bounds.firstWindow ?? (await windowFrom(cursorBlock, bounds.firstMatchingBlock))
|
|
3825
3864
|
|
|
3826
3865
|
// Slide to the next disjoint window away from the newest block and reset
|
|
3827
3866
|
// the cursor: every row in the next window is already past the cursor
|
|
3828
3867
|
// anchor in the requested order, so no anchor refinement is needed.
|
|
3829
|
-
const slideWindow = async () => {
|
|
3868
|
+
const slideWindow = async (indexedHeadAnchor?: number) => {
|
|
3830
3869
|
activeCursor = undefined
|
|
3870
|
+
if (window === 'indexedHead') {
|
|
3871
|
+
const matchingBlock = await findMatchingBlock(indexedHeadAnchor)
|
|
3872
|
+
window =
|
|
3873
|
+
matchingBlock === undefined ? undefined : await windowFrom(matchingBlock, matchingBlock)
|
|
3874
|
+
return
|
|
3875
|
+
}
|
|
3831
3876
|
window =
|
|
3832
3877
|
window === undefined
|
|
3833
3878
|
? undefined
|
|
@@ -3863,7 +3908,7 @@ async function getSwaps(c: Context<App.Environment>, options: getSwaps.Options)
|
|
|
3863
3908
|
// A short batch exhausts this window; a full one may leave more groups
|
|
3864
3909
|
// in it, so advance the keyset cursor and re-query the same window.
|
|
3865
3910
|
if (!fetchedFull) {
|
|
3866
|
-
await slideWindow()
|
|
3911
|
+
await slideWindow(order === 'desc' ? tail.blockNumber - 1 : tail.blockNumber + 1)
|
|
3867
3912
|
continue
|
|
3868
3913
|
}
|
|
3869
3914
|
const next = cursorAfterWholeGroup(tail)
|
|
@@ -3896,13 +3941,7 @@ async function getSwaps(c: Context<App.Environment>, options: getSwaps.Options)
|
|
|
3896
3941
|
])
|
|
3897
3942
|
: null
|
|
3898
3943
|
|
|
3899
|
-
|
|
3900
|
-
// Recover each swap's `mode` from transaction calldata — deferred to here
|
|
3901
|
-
// so the candidate scan never pays the `txs` lookup for rows beyond the
|
|
3902
|
-
// page. Best-effort: a failed lookup leaves `mode: null`.
|
|
3903
|
-
await resolveSwapModes(c, { chainId, swaps: data })
|
|
3904
|
-
|
|
3905
|
-
return { data, nextCursor }
|
|
3944
|
+
return { data: pageRows.map((candidate) => candidate.swap), nextCursor }
|
|
3906
3945
|
})
|
|
3907
3946
|
}
|
|
3908
3947
|
|
|
@@ -4054,6 +4093,8 @@ declare namespace parseGroupRow {
|
|
|
4054
4093
|
* history compressed to the latest event (`argMax`, sentinel position
|
|
4055
4094
|
* `(-1, -1)` so it sorts first) plus every raw in-page event, so an order
|
|
4056
4095
|
* that flips between two of its page fills resolves each fill correctly.
|
|
4096
|
+
* The same query attaches each book's indexed quote token, avoiding a second
|
|
4097
|
+
* TIDX request; callers retain an RPC fallback for books absent from the index.
|
|
4057
4098
|
* `logs` is sorted by `(address, selector, block_num, log_idx)` with a
|
|
4058
4099
|
* `topic1` bloom index, so both halves are pruned range reads. Returned
|
|
4059
4100
|
* per-order event lists are position-sorted.
|
|
@@ -4078,16 +4119,27 @@ export async function resolveOrderStates(
|
|
|
4078
4119
|
chainId,
|
|
4079
4120
|
engine: 'clickhouse',
|
|
4080
4121
|
query: `
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4122
|
+
WITH states AS (
|
|
4123
|
+
SELECT topic1, argMax(topic3, (block_num, log_idx)) AS t3,
|
|
4124
|
+
argMax(data, (block_num, log_idx)) AS payload,
|
|
4125
|
+
toInt64(-1) AS bn, toInt32(-1) AS li
|
|
4126
|
+
FROM logs
|
|
4127
|
+
WHERE ${eventFilter} AND block_num < ${minBlock}
|
|
4128
|
+
GROUP BY topic1
|
|
4129
|
+
UNION ALL
|
|
4130
|
+
SELECT topic1, topic3 AS t3, data AS payload, block_num AS bn, log_idx AS li
|
|
4131
|
+
FROM logs
|
|
4132
|
+
WHERE ${eventFilter} AND block_num >= ${minBlock} AND block_num <= ${maxBlock}
|
|
4133
|
+
)
|
|
4134
|
+
SELECT states.*, pairs.quote AS quote_token
|
|
4135
|
+
FROM states
|
|
4136
|
+
LEFT JOIN (
|
|
4137
|
+
-- Preserve the oldest-pair rule without sorting the complete table.
|
|
4138
|
+
SELECT base, argMin(quote, (block_num, log_idx)) AS quote
|
|
4139
|
+
FROM dex_pairs
|
|
4140
|
+
WHERE address = '${stablecoinDex}'
|
|
4141
|
+
GROUP BY base
|
|
4142
|
+
) AS pairs ON pairs.base = concat('0x', lower(substring(states.t3, 27)))
|
|
4091
4143
|
` as string,
|
|
4092
4144
|
})
|
|
4093
4145
|
|
|
@@ -4123,6 +4175,8 @@ declare namespace resolveOrderStates {
|
|
|
4123
4175
|
isBid: boolean
|
|
4124
4176
|
/** Event log index, or `-1` for the baseline. */
|
|
4125
4177
|
logIndex: number
|
|
4178
|
+
/** Indexed quote token for the state's book, when available. */
|
|
4179
|
+
quoteToken?: z.output<typeof Schema.TokenAddress> | undefined
|
|
4126
4180
|
/** Signed price tick at this state. */
|
|
4127
4181
|
tick: number
|
|
4128
4182
|
/** Book base token (lowercase address). */
|
|
@@ -4144,6 +4198,7 @@ function parseStateRow(
|
|
|
4144
4198
|
const payload = Value.toText(row['payload'])
|
|
4145
4199
|
const blockNumber = Value.toNumber(row['bn'])
|
|
4146
4200
|
const logIndex = Value.toNumber(row['li'])
|
|
4201
|
+
const quoteToken = Schema.TokenAddress.safeParse(row['quote_token'])
|
|
4147
4202
|
if (
|
|
4148
4203
|
topic1 === undefined ||
|
|
4149
4204
|
!/^0x[0-9a-fA-F]{64}$/.test(topic1) ||
|
|
@@ -4166,6 +4221,7 @@ function parseStateRow(
|
|
|
4166
4221
|
blockNumber,
|
|
4167
4222
|
isBid,
|
|
4168
4223
|
logIndex,
|
|
4224
|
+
...(quoteToken.success ? { quoteToken: quoteToken.data } : {}),
|
|
4169
4225
|
tick,
|
|
4170
4226
|
token: `0x${topic3.slice(26).toLowerCase()}`,
|
|
4171
4227
|
},
|
|
@@ -4718,15 +4774,10 @@ const globalOrderScanCap = 2000
|
|
|
4718
4774
|
|
|
4719
4775
|
/**
|
|
4720
4776
|
* Batch size for `OrderCancelled` / `OrderFilled` `WHERE "orderId" IN (...)`
|
|
4721
|
-
* lookups.
|
|
4722
|
-
*
|
|
4723
|
-
* `tempoxyz/ak47` commit `273d88d`); very large lists still inflate SQL parse
|
|
4724
|
-
* cost, but each batch is also an independent transient-error round trip, so
|
|
4725
|
-
* 1000 balances parse cost against keeping the batch count (and thus the
|
|
4726
|
-
* failure surface) low — a typical pair snapshot still resolves in one trip and
|
|
4727
|
-
* the capped global feed in ~two.
|
|
4777
|
+
* lookups. A 2000-ID batch uses indexed state paths while native fill
|
|
4778
|
+
* aggregation keeps the response below SQL and result limits.
|
|
4728
4779
|
*/
|
|
4729
|
-
const orderInBatchSize =
|
|
4780
|
+
const orderInBatchSize = 2000
|
|
4730
4781
|
|
|
4731
4782
|
/**
|
|
4732
4783
|
* Signed-int16 bias used to encode `tick` in the cursor as a non-negative
|
|
@@ -4745,8 +4796,8 @@ const orderCursorTickBias = 32768
|
|
|
4745
4796
|
* when scoped (string column with a bloom-filter index on `topic1`), and the
|
|
4746
4797
|
* fill / cancellation joins use `WHERE "orderId" IN (...)` against the same
|
|
4747
4798
|
* index. The decoded `tick` column has no ClickHouse index, so tick sort is
|
|
4748
|
-
* still applied app-side.
|
|
4749
|
-
*
|
|
4799
|
+
* still applied app-side. ClickHouse aggregates fills per order before the
|
|
4800
|
+
* app computes each remaining amount.
|
|
4750
4801
|
*
|
|
4751
4802
|
* `remaining = OrderPlaced.amount - sum(OrderFilled.amountFilled)`; orders
|
|
4752
4803
|
* with an `OrderCancelled` event are excluded outright. Each row is annotated
|
|
@@ -5012,7 +5063,7 @@ declare namespace getPairDepth {
|
|
|
5012
5063
|
chainId: z.output<typeof Schema.ChainId>
|
|
5013
5064
|
/** Maximum number of non-empty levels to keep per side, in best-first order. */
|
|
5014
5065
|
levels: number
|
|
5015
|
-
pair:
|
|
5066
|
+
pair: getPairsByBase.Pair
|
|
5016
5067
|
}
|
|
5017
5068
|
}
|
|
5018
5069
|
|
|
@@ -5362,7 +5413,7 @@ async function fetchCancelledByIds(
|
|
|
5362
5413
|
query: `
|
|
5363
5414
|
SELECT "orderId"
|
|
5364
5415
|
FROM OrderCancelled
|
|
5365
|
-
WHERE "orderId" IN (${batch.join(', ')})
|
|
5416
|
+
WHERE address = '${stablecoinDex}' AND "orderId" IN (${batch.join(', ')})
|
|
5366
5417
|
`,
|
|
5367
5418
|
signatures: [orderCancelledSignature],
|
|
5368
5419
|
})
|
|
@@ -5375,10 +5426,9 @@ async function fetchCancelledByIds(
|
|
|
5375
5426
|
}
|
|
5376
5427
|
|
|
5377
5428
|
/**
|
|
5378
|
-
* Sums `amountFilled` per `orderId` across all matching `
|
|
5379
|
-
* the snapshot window via batched `WHERE "orderId" IN (...)` queries.
|
|
5380
|
-
*
|
|
5381
|
-
* (indexer-side `SUM(...)` over decoded columns is currently unreliable).
|
|
5429
|
+
* Sums `amountFilled` per `orderId` across all matching native `dex_fills`
|
|
5430
|
+
* rows in the snapshot window via batched `WHERE "orderId" IN (...)` queries.
|
|
5431
|
+
* Native aggregation bounds results, and stringification preserves UInt256 precision through JSON parsing.
|
|
5382
5432
|
*/
|
|
5383
5433
|
async function fetchFillSumsByIds(
|
|
5384
5434
|
c: Context<App.Environment>,
|
|
@@ -5396,17 +5446,19 @@ async function fetchFillSumsByIds(
|
|
|
5396
5446
|
const result = await tidx.fetch({
|
|
5397
5447
|
chainId,
|
|
5398
5448
|
engine: 'clickhouse',
|
|
5449
|
+
// Dynamic order IDs prevent signature-backed row inference; each aggregate is validated below before use.
|
|
5399
5450
|
query: `
|
|
5400
|
-
SELECT "orderId", "amountFilled"
|
|
5451
|
+
SELECT "orderId", toString(sum("amountFilled")) AS amount_filled
|
|
5401
5452
|
FROM dex_fills
|
|
5402
|
-
WHERE "orderId" IN (${batch.join(', ')})
|
|
5453
|
+
WHERE address = '${stablecoinDex}' AND "orderId" IN (${batch.join(', ')})
|
|
5454
|
+
GROUP BY "orderId"
|
|
5403
5455
|
` as string,
|
|
5404
5456
|
})
|
|
5405
5457
|
for (const row of result.rows) {
|
|
5406
5458
|
const id = Value.toIntegerString(row['orderId'])
|
|
5407
|
-
const filled = Value.toIntegerString(row['
|
|
5459
|
+
const filled = Value.toIntegerString(row['amount_filled'])
|
|
5408
5460
|
if (id === undefined || filled === undefined) continue
|
|
5409
|
-
sums.set(id,
|
|
5461
|
+
sums.set(id, BigInt(filled))
|
|
5410
5462
|
}
|
|
5411
5463
|
}
|
|
5412
5464
|
return sums
|