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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Context, Hono } from 'hono'
|
|
2
2
|
import { Value as core_Value } from 'ox'
|
|
3
|
+
import type { Actions } from 'viem/tempo'
|
|
3
4
|
import {
|
|
4
5
|
type Chain,
|
|
5
6
|
ContractFunctionExecutionError,
|
|
@@ -57,6 +58,12 @@ const venueSharesDepositedSignature =
|
|
|
57
58
|
const maxScannedDeployments = 250
|
|
58
59
|
const scanLimit = 50
|
|
59
60
|
|
|
61
|
+
// Concurrent metadata reads in one macrotask collapse into one deployless
|
|
62
|
+
// multicall; an await between small chunks keeps each call under the RPC limit.
|
|
63
|
+
const tokenReadChunkSize = 20
|
|
64
|
+
/** Block boundary that includes all indexed history. */
|
|
65
|
+
const beforeFirstBlockNumber = -1
|
|
66
|
+
|
|
60
67
|
/** Vault TVL is reported in USD only; the API exposes no denomination parameter. */
|
|
61
68
|
const tvlDenomination = 'USD'
|
|
62
69
|
|
|
@@ -155,7 +162,9 @@ export namespace schema {
|
|
|
155
162
|
z.describe('Tokens accepted by private deposits.'),
|
|
156
163
|
z.meta({ examples: [['0x20c000000000000000000000ff04042ee92fd449']] }),
|
|
157
164
|
),
|
|
158
|
-
name: z
|
|
165
|
+
name: z
|
|
166
|
+
.string()
|
|
167
|
+
.check(z.describe('Private Zone name.'), z.meta({ examples: ['zone-moderato'] })),
|
|
159
168
|
outputTokens: z
|
|
160
169
|
.array(Schema.Address)
|
|
161
170
|
.check(
|
|
@@ -228,7 +237,7 @@ export namespace schema {
|
|
|
228
237
|
.nullable(value)
|
|
229
238
|
.check(
|
|
230
239
|
z.describe(
|
|
231
|
-
'Assets returned for one whole
|
|
240
|
+
'Assets returned for one whole earn share, in the asset display currency, or null when the engine cannot quote an exit.',
|
|
232
241
|
),
|
|
233
242
|
),
|
|
234
243
|
shareToken: Tokens.schema.Token.check(z.describe('Token representing vault shares.')),
|
|
@@ -253,7 +262,7 @@ export namespace schema {
|
|
|
253
262
|
verified: z
|
|
254
263
|
.boolean()
|
|
255
264
|
.check(
|
|
256
|
-
z.describe('Whether this vault is in the curated
|
|
265
|
+
z.describe('Whether this vault is in the curated earn registry.'),
|
|
257
266
|
z.meta({ examples: [true] }),
|
|
258
267
|
),
|
|
259
268
|
zone: z
|
|
@@ -262,7 +271,7 @@ export namespace schema {
|
|
|
262
271
|
})
|
|
263
272
|
|
|
264
273
|
/** One compatible Earn vault resolved from current chain state. */
|
|
265
|
-
export const Vault = Schema.describe(vault, 'A compatible
|
|
274
|
+
export const Vault = Schema.describe(vault, 'A compatible earn vault resolved from chain state.')
|
|
266
275
|
|
|
267
276
|
/** One registry-curated Earn vault. */
|
|
268
277
|
export const VerifiedVault = Schema.describe(
|
|
@@ -277,16 +286,16 @@ export namespace schema {
|
|
|
277
286
|
verified: z
|
|
278
287
|
.literal(true)
|
|
279
288
|
.check(
|
|
280
|
-
z.describe('This vault is in the curated
|
|
289
|
+
z.describe('This vault is in the curated earn registry.'),
|
|
281
290
|
z.meta({ examples: [true] }),
|
|
282
291
|
),
|
|
283
292
|
}),
|
|
284
|
-
'A registry-curated
|
|
293
|
+
'A registry-curated earn vault resolved from chain state.',
|
|
285
294
|
)
|
|
286
295
|
|
|
287
296
|
const position = z.strictObject({
|
|
288
297
|
account: Schema.Address.check(
|
|
289
|
-
z.describe('Account whose
|
|
298
|
+
z.describe('Account whose earn position was read.'),
|
|
290
299
|
z.meta({ examples: ['0xbe058e1c4df8a4366a387bf595b284246a93039e'] }),
|
|
291
300
|
),
|
|
292
301
|
assetAllowance: Schema.DecimalString.check(
|
|
@@ -313,16 +322,49 @@ export namespace schema {
|
|
|
313
322
|
z.meta({ examples: ['99500000'] }),
|
|
314
323
|
),
|
|
315
324
|
shareToken: Schema.tokenAddress('0x20c000000000000000000000300e14ab91a10769').check(
|
|
316
|
-
z.describe('TIP-20 token representing
|
|
325
|
+
z.describe('TIP-20 token representing earn shares.'),
|
|
317
326
|
),
|
|
318
327
|
value: Schema.DecimalString.check(
|
|
319
328
|
z.describe(
|
|
320
|
-
'Current asset value of this account’s
|
|
329
|
+
'Current asset value of this account’s earn shares, including fees, in asset base units.',
|
|
321
330
|
),
|
|
322
331
|
z.meta({ examples: ['100000000'] }),
|
|
323
332
|
),
|
|
324
333
|
})
|
|
325
334
|
|
|
335
|
+
const addressPosition = z.strictObject({
|
|
336
|
+
assetAmount: value.check(
|
|
337
|
+
z.describe('Current asset value of the held shares, including fees, in `assetToken` units.'),
|
|
338
|
+
),
|
|
339
|
+
assetToken: Tokens.schema.Token.check(z.describe('Asset accepted by the vault.')),
|
|
340
|
+
id: Schema.Address.check(
|
|
341
|
+
z.describe('Stable resource ID for this position, equal to the vault address.'),
|
|
342
|
+
z.meta({ examples: ['0xf4ae63687d6753a78e7f551d2eda1d0d31a5ea3a'] }),
|
|
343
|
+
),
|
|
344
|
+
shareAmount: value.check(
|
|
345
|
+
z.describe('Earn share balance this account holds, in `shareToken` units.'),
|
|
346
|
+
),
|
|
347
|
+
shareToken: Tokens.schema.Token.check(z.describe('Token representing vault shares.')),
|
|
348
|
+
valuation: z
|
|
349
|
+
.optional(z.nullable(Valuation.schema.Value))
|
|
350
|
+
.check(
|
|
351
|
+
z.describe(
|
|
352
|
+
'The position’s nominal value when `valuation.currency` is requested, or `null` when ' +
|
|
353
|
+
'the asset token is unverified, its display currency has no rate, or rates were unavailable.',
|
|
354
|
+
),
|
|
355
|
+
),
|
|
356
|
+
vaultAddress: Schema.Address.check(
|
|
357
|
+
z.describe('Earn vault contract address holding this position.'),
|
|
358
|
+
z.meta({ examples: ['0xf4ae63687d6753a78e7f551d2eda1d0d31a5ea3a'] }),
|
|
359
|
+
),
|
|
360
|
+
verified: z
|
|
361
|
+
.boolean()
|
|
362
|
+
.check(
|
|
363
|
+
z.describe('Whether the vault is in the curated earn registry.'),
|
|
364
|
+
z.meta({ examples: [true] }),
|
|
365
|
+
),
|
|
366
|
+
})
|
|
367
|
+
|
|
326
368
|
const earningsBase = z.strictObject({
|
|
327
369
|
account: Schema.Address.check(
|
|
328
370
|
z.describe('Account whose vault earnings were calculated.'),
|
|
@@ -334,7 +376,7 @@ export namespace schema {
|
|
|
334
376
|
),
|
|
335
377
|
currentValue: Schema.DecimalString.check(
|
|
336
378
|
z.describe(
|
|
337
|
-
'Asset value of the account’s active
|
|
379
|
+
'Asset value of the account’s active earn shares at the latest indexed block, in asset base units.',
|
|
338
380
|
),
|
|
339
381
|
z.meta({ examples: ['600000'] }),
|
|
340
382
|
),
|
|
@@ -345,8 +387,13 @@ export namespace schema {
|
|
|
345
387
|
})
|
|
346
388
|
|
|
347
389
|
const earningsPeriod = z
|
|
348
|
-
.enum(['active', 'lifetime'])
|
|
349
|
-
.check(
|
|
390
|
+
.enum(['30d', 'active', 'lifetime'])
|
|
391
|
+
.check(
|
|
392
|
+
z.describe(
|
|
393
|
+
'Earnings period to calculate: trailing 30 days, shares still held, or lifetime history.',
|
|
394
|
+
),
|
|
395
|
+
z.meta({ examples: ['lifetime'] }),
|
|
396
|
+
)
|
|
350
397
|
|
|
351
398
|
const earnings = z.union([
|
|
352
399
|
z.extend(earningsBase, {
|
|
@@ -395,22 +442,45 @@ export namespace schema {
|
|
|
395
442
|
z.meta({ examples: ['complete'] }),
|
|
396
443
|
),
|
|
397
444
|
}),
|
|
445
|
+
z.extend(earningsBase, {
|
|
446
|
+
period: z
|
|
447
|
+
.literal('30d')
|
|
448
|
+
.check(
|
|
449
|
+
z.describe('Returns cash-flow-adjusted earnings over the trailing 30 days.'),
|
|
450
|
+
z.meta({ examples: ['30d'] }),
|
|
451
|
+
),
|
|
452
|
+
status: z
|
|
453
|
+
.literal('complete')
|
|
454
|
+
.check(
|
|
455
|
+
z.describe('Earnings include a complete asset-denominated cost basis.'),
|
|
456
|
+
z.meta({ examples: ['complete'] }),
|
|
457
|
+
),
|
|
458
|
+
windowEarnings: z
|
|
459
|
+
.string()
|
|
460
|
+
.check(
|
|
461
|
+
z.regex(/^-?\d+$/),
|
|
462
|
+
z.describe(
|
|
463
|
+
'Ending share value plus realized assets, minus deposits and opening share value, over the trailing 30 days.',
|
|
464
|
+
),
|
|
465
|
+
z.meta({ examples: ['0'] }),
|
|
466
|
+
),
|
|
467
|
+
}),
|
|
398
468
|
z.extend(earningsBase, {
|
|
399
469
|
period: earningsPeriod,
|
|
400
470
|
status: z
|
|
401
471
|
.literal('incomplete_cost_basis')
|
|
402
472
|
.check(
|
|
403
473
|
z.describe(
|
|
404
|
-
'Earnings are unavailable when indexed activity cannot reconstruct the
|
|
474
|
+
'Earnings are unavailable when indexed activity cannot reconstruct the asset cost basis for the requested period.',
|
|
405
475
|
),
|
|
406
476
|
z.meta({ examples: ['incomplete_cost_basis'] }),
|
|
407
477
|
),
|
|
408
478
|
}),
|
|
409
479
|
z.extend(earningsBase, {
|
|
410
480
|
period: z
|
|
411
|
-
.
|
|
481
|
+
.enum(['30d', 'lifetime'])
|
|
412
482
|
.check(
|
|
413
|
-
z.describe('
|
|
483
|
+
z.describe('Earnings period with an unresolved async redemption.'),
|
|
414
484
|
z.meta({ examples: ['lifetime'] }),
|
|
415
485
|
),
|
|
416
486
|
status: z
|
|
@@ -453,7 +523,7 @@ export namespace schema {
|
|
|
453
523
|
|
|
454
524
|
const collectionQuery = z
|
|
455
525
|
.extend(collectionFilters, { 'apy.window': EarnRates.schema.Window })
|
|
456
|
-
.check(z.describe('Query parameters for listing
|
|
526
|
+
.check(z.describe('Query parameters for listing earn vaults.'))
|
|
457
527
|
|
|
458
528
|
const vaultSelector = z.strictObject({
|
|
459
529
|
chainId: Schema.ChainIdQuery,
|
|
@@ -470,11 +540,11 @@ export namespace schema {
|
|
|
470
540
|
.strictObject({
|
|
471
541
|
data: z
|
|
472
542
|
.array(Vault)
|
|
473
|
-
.check(z.describe('Compatible deployed
|
|
543
|
+
.check(z.describe('Compatible deployed earn vaults in deployment order.')),
|
|
474
544
|
meta: z.optional(meta),
|
|
475
545
|
nextCursor: Schema.NextCursor,
|
|
476
546
|
})
|
|
477
|
-
.check(z.describe('A page of compatible deployed
|
|
547
|
+
.check(z.describe('A page of compatible deployed earn vaults.'))
|
|
478
548
|
}
|
|
479
549
|
|
|
480
550
|
/** Schemas for the getVerifiedEarnVaults operation. */
|
|
@@ -482,7 +552,7 @@ export namespace schema {
|
|
|
482
552
|
/** Collection query without rates or valuation, for surfaces that cannot measure them. */
|
|
483
553
|
export const BaseQuery = Schema.describe(
|
|
484
554
|
z.extend(collectionFilters, { include: baseIncludeQuery }),
|
|
485
|
-
'Query parameters for listing
|
|
555
|
+
'Query parameters for listing earn vaults.',
|
|
486
556
|
)
|
|
487
557
|
|
|
488
558
|
/** Query parameters for registry-curated Earn vaults. */
|
|
@@ -493,37 +563,95 @@ export namespace schema {
|
|
|
493
563
|
.strictObject({
|
|
494
564
|
data: z
|
|
495
565
|
.array(VerifiedVault)
|
|
496
|
-
.check(z.describe('Registry-curated
|
|
566
|
+
.check(z.describe('Registry-curated earn vaults ordered by vault address.')),
|
|
497
567
|
meta: z.optional(meta),
|
|
498
568
|
nextCursor: Schema.NextCursor,
|
|
499
569
|
})
|
|
500
|
-
.check(z.describe('A page of registry-curated
|
|
570
|
+
.check(z.describe('A page of registry-curated earn vaults.'))
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/** Schemas for the getEarnAddressPositions operation. */
|
|
574
|
+
export namespace getEarnAddressPositions {
|
|
575
|
+
/** Path parameters identifying the account. */
|
|
576
|
+
export const Params = z
|
|
577
|
+
.strictObject({
|
|
578
|
+
address: Schema.Address.check(z.describe('Account whose earn positions to list.')),
|
|
579
|
+
})
|
|
580
|
+
.check(z.describe('Path parameters identifying an account with earn positions.'))
|
|
581
|
+
|
|
582
|
+
/** Query parameters for listing an account's earn positions. */
|
|
583
|
+
export const Query = z
|
|
584
|
+
.strictObject({
|
|
585
|
+
chainId: Schema.ChainIdQuery,
|
|
586
|
+
cursor: Schema.Cursor,
|
|
587
|
+
limit: Schema.Limit,
|
|
588
|
+
'valuation.currency': Schema.Denomination.check(
|
|
589
|
+
z.describe(
|
|
590
|
+
'When present, include each position’s nominal value in this denomination. ' +
|
|
591
|
+
'Case-insensitive and must be priced by the configured FX oracle.',
|
|
592
|
+
),
|
|
593
|
+
),
|
|
594
|
+
verified: z
|
|
595
|
+
.optional(Schema.booleanQuery())
|
|
596
|
+
.check(
|
|
597
|
+
z.describe(
|
|
598
|
+
'When `true`, include only positions in registry-curated (verified) earn vaults.',
|
|
599
|
+
),
|
|
600
|
+
z.meta({ examples: [true] }),
|
|
601
|
+
),
|
|
602
|
+
})
|
|
603
|
+
.check(z.describe('Query parameters for listing an account’s earn positions.'))
|
|
604
|
+
|
|
605
|
+
/** Page-level valuation rate provenance. */
|
|
606
|
+
export const Meta = z
|
|
607
|
+
.object({
|
|
608
|
+
valuation: z
|
|
609
|
+
.optional(Valuation.schema.Pricing)
|
|
610
|
+
.check(
|
|
611
|
+
z.describe(
|
|
612
|
+
'Rate provenance for row valuations. Present when conversion rates were ' +
|
|
613
|
+
'consulted; absent when every value was identity-valued or rates were unavailable.',
|
|
614
|
+
),
|
|
615
|
+
),
|
|
616
|
+
})
|
|
617
|
+
.check(z.describe('Page-level valuation rate provenance.'))
|
|
618
|
+
|
|
619
|
+
/** Paginated list of the account's earn vault positions. */
|
|
620
|
+
export const Response = z
|
|
621
|
+
.strictObject({
|
|
622
|
+
data: z
|
|
623
|
+
.array(addressPosition)
|
|
624
|
+
.check(z.describe('Vaults where the account holds earn shares.')),
|
|
625
|
+
meta: z.optional(Meta),
|
|
626
|
+
nextCursor: Schema.NextCursor,
|
|
627
|
+
})
|
|
628
|
+
.check(z.describe('A page of the account’s earn vault positions.'))
|
|
501
629
|
}
|
|
502
630
|
|
|
503
631
|
/** Schemas for the getEarnVault operation. */
|
|
504
632
|
export namespace getEarnVault {
|
|
505
|
-
/** Path parameters identifying one
|
|
633
|
+
/** Path parameters identifying one earn vault. */
|
|
506
634
|
export const Params = z
|
|
507
635
|
.strictObject({
|
|
508
636
|
vaultId: Schema.Address.check(z.describe('Earn vault contract address.')),
|
|
509
637
|
})
|
|
510
|
-
.check(z.describe('Path parameters identifying one
|
|
638
|
+
.check(z.describe('Path parameters identifying one earn vault.'))
|
|
511
639
|
|
|
512
640
|
/** Detail query without rates or valuation, for surfaces that cannot measure them. */
|
|
513
641
|
export const BaseQuery = Schema.describe(
|
|
514
642
|
z.extend(vaultSelector, { include: baseIncludeQuery }),
|
|
515
|
-
'Query parameters selecting one
|
|
643
|
+
'Query parameters selecting one earn vault.',
|
|
516
644
|
)
|
|
517
645
|
|
|
518
646
|
/** Query parameters selecting the chain containing the vault. */
|
|
519
647
|
export const Query = z
|
|
520
648
|
.extend(vaultSelector, { 'apy.window': EarnRates.schema.Window })
|
|
521
|
-
.check(z.describe('Query parameters selecting one
|
|
649
|
+
.check(z.describe('Query parameters selecting one earn vault.'))
|
|
522
650
|
|
|
523
651
|
/** One compatible Earn vault resolved directly from current chain state. */
|
|
524
652
|
export const Response = Schema.describe(
|
|
525
653
|
z.extend(vault, { meta: z.optional(meta) }),
|
|
526
|
-
'A compatible
|
|
654
|
+
'A compatible earn vault resolved from chain state, with rate provenance for its valued fields.',
|
|
527
655
|
)
|
|
528
656
|
}
|
|
529
657
|
|
|
@@ -532,22 +660,22 @@ export namespace schema {
|
|
|
532
660
|
/** Path parameters identifying the Earn vault and account. */
|
|
533
661
|
export const Params = z
|
|
534
662
|
.strictObject({
|
|
535
|
-
address: Schema.Address.check(z.describe('Account whose
|
|
663
|
+
address: Schema.Address.check(z.describe('Account whose earn position to read.')),
|
|
536
664
|
vaultId: Schema.address('0xdb48b00fb28d9a8db881fcd671c3fb1bf8c486b6').check(
|
|
537
665
|
z.describe('Earn vault contract address.'),
|
|
538
666
|
),
|
|
539
667
|
})
|
|
540
|
-
.check(z.describe('Path parameters identifying an
|
|
668
|
+
.check(z.describe('Path parameters identifying an earn vault position.'))
|
|
541
669
|
|
|
542
670
|
/** Query parameters selecting the chain containing the vault. */
|
|
543
671
|
export const Query = z
|
|
544
672
|
.strictObject({ chainId: Schema.ChainIdQuery })
|
|
545
|
-
.check(z.describe('Query parameters selecting an
|
|
673
|
+
.check(z.describe('Query parameters selecting an earn vault position.'))
|
|
546
674
|
|
|
547
675
|
/** Current asset and Earn-share state for one account in one vault. */
|
|
548
676
|
export const Response = Schema.describe(
|
|
549
677
|
position,
|
|
550
|
-
'Current asset and
|
|
678
|
+
'Current asset and earn-share balances, allowances, and asset value for an account in one earn vault.',
|
|
551
679
|
)
|
|
552
680
|
}
|
|
553
681
|
|
|
@@ -561,7 +689,7 @@ export namespace schema {
|
|
|
561
689
|
z.describe('Earn vault contract address.'),
|
|
562
690
|
),
|
|
563
691
|
})
|
|
564
|
-
.check(z.describe('Path parameters identifying an
|
|
692
|
+
.check(z.describe('Path parameters identifying an earn vault account.'))
|
|
565
693
|
|
|
566
694
|
/** Query parameters selecting the chain containing the vault. */
|
|
567
695
|
export const Query = z
|
|
@@ -569,12 +697,12 @@ export namespace schema {
|
|
|
569
697
|
chainId: Schema.ChainIdQuery,
|
|
570
698
|
period: z._default(earningsPeriod, 'lifetime'),
|
|
571
699
|
})
|
|
572
|
-
.check(z.describe('Query parameters selecting an
|
|
700
|
+
.check(z.describe('Query parameters selecting an earn vault account.'))
|
|
573
701
|
|
|
574
702
|
/** Earnings for one account and period in one Earn vault. */
|
|
575
703
|
export const Response = Schema.describe(
|
|
576
704
|
earnings,
|
|
577
|
-
'Indexed asset value and cost-basis-aware earnings for an account in one
|
|
705
|
+
'Indexed asset value and cost-basis-aware earnings for an account in one earn vault.',
|
|
578
706
|
)
|
|
579
707
|
}
|
|
580
708
|
}
|
|
@@ -592,12 +720,12 @@ export function earn(options: earn.Options = {}) {
|
|
|
592
720
|
}),
|
|
593
721
|
OpenApi.describeRoute({
|
|
594
722
|
description:
|
|
595
|
-
'Lists compatible
|
|
723
|
+
'Lists compatible earn vaults discovered from deployment events and resolved from current chain state.',
|
|
596
724
|
operationId: 'getEarnVaults',
|
|
597
725
|
responses: OpenApi.responses({
|
|
598
|
-
errors: { 502: 'Could not read
|
|
726
|
+
errors: { 502: 'Could not read earn deployment or vault data.' },
|
|
599
727
|
success: {
|
|
600
|
-
description: 'A page of compatible deployed
|
|
728
|
+
description: 'A page of compatible deployed earn vaults.',
|
|
601
729
|
schema: schema.getEarnVaults.Response,
|
|
602
730
|
},
|
|
603
731
|
}),
|
|
@@ -638,12 +766,12 @@ export function earn(options: earn.Options = {}) {
|
|
|
638
766
|
}),
|
|
639
767
|
OpenApi.describeRoute({
|
|
640
768
|
description:
|
|
641
|
-
'Lists registry-curated
|
|
769
|
+
'Lists registry-curated earn vaults after resolving their current onchain configuration.',
|
|
642
770
|
operationId: 'getVerifiedEarnVaults',
|
|
643
771
|
responses: OpenApi.responses({
|
|
644
|
-
errors: { 502: 'Could not read curated
|
|
772
|
+
errors: { 502: 'Could not read curated earn registry or vault data.' },
|
|
645
773
|
success: {
|
|
646
|
-
description: 'A page of registry-curated
|
|
774
|
+
description: 'A page of registry-curated earn vaults.',
|
|
647
775
|
schema: schema.getVerifiedEarnVaults.Response,
|
|
648
776
|
},
|
|
649
777
|
}),
|
|
@@ -673,6 +801,86 @@ export function earn(options: earn.Options = {}) {
|
|
|
673
801
|
}
|
|
674
802
|
},
|
|
675
803
|
)
|
|
804
|
+
.get(
|
|
805
|
+
'/v1/earn/addresses/:address/positions',
|
|
806
|
+
Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }),
|
|
807
|
+
OpenApi.validate('param', schema.getEarnAddressPositions.Params, {
|
|
808
|
+
code: 'positions_invalid',
|
|
809
|
+
message: 'Invalid address positions',
|
|
810
|
+
}),
|
|
811
|
+
OpenApi.validate('query', schema.getEarnAddressPositions.Query, {
|
|
812
|
+
code: 'query_invalid',
|
|
813
|
+
message: 'Invalid query parameters',
|
|
814
|
+
}),
|
|
815
|
+
OpenApi.describeRoute({
|
|
816
|
+
description:
|
|
817
|
+
'Lists every earn vault where an account currently holds shares, with the current balance and value of each position.',
|
|
818
|
+
operationId: 'getEarnAddressPositions',
|
|
819
|
+
responses: OpenApi.responses({
|
|
820
|
+
errors: {
|
|
821
|
+
400: {
|
|
822
|
+
codes: [
|
|
823
|
+
'chain_id_invalid',
|
|
824
|
+
'chain_id_unsupported',
|
|
825
|
+
'positions_invalid',
|
|
826
|
+
'query_invalid',
|
|
827
|
+
],
|
|
828
|
+
},
|
|
829
|
+
502: 'Could not read earn deployment or position data.',
|
|
830
|
+
},
|
|
831
|
+
success: {
|
|
832
|
+
description: 'A page of the account’s earn vault positions.',
|
|
833
|
+
schema: schema.getEarnAddressPositions.Response,
|
|
834
|
+
},
|
|
835
|
+
}),
|
|
836
|
+
summary: 'List account positions',
|
|
837
|
+
tags: ['Earn'],
|
|
838
|
+
}),
|
|
839
|
+
// `noStore` matches the address balances listing: account holdings must
|
|
840
|
+
// read fresh immediately after a deposit or redemption.
|
|
841
|
+
Cache.response<App.Environment>({
|
|
842
|
+
cacheControl: Cache.policies.noStore,
|
|
843
|
+
key: (c) => Cache.urlKey(c, schema.getEarnAddressPositions.Query),
|
|
844
|
+
name: 'tempo-api:earn-address-positions:v1',
|
|
845
|
+
}),
|
|
846
|
+
async (c) => {
|
|
847
|
+
if (Auth.narrowAccess) return Auth.paidAccessError(c)
|
|
848
|
+
if (OpenApi.narrowValidation)
|
|
849
|
+
return Response.error(c, {
|
|
850
|
+
code: 'chain_id_invalid',
|
|
851
|
+
message: 'Invalid chain id',
|
|
852
|
+
status: 400,
|
|
853
|
+
})
|
|
854
|
+
if (OpenApi.narrowValidation)
|
|
855
|
+
return Response.error(c, {
|
|
856
|
+
code: 'chain_id_unsupported',
|
|
857
|
+
message: 'Unsupported chain id',
|
|
858
|
+
status: 400,
|
|
859
|
+
})
|
|
860
|
+
if (OpenApi.narrowValidation)
|
|
861
|
+
return OpenApi.validationError(c, {
|
|
862
|
+
code: 'positions_invalid',
|
|
863
|
+
message: 'Invalid request parameters',
|
|
864
|
+
})
|
|
865
|
+
if (OpenApi.narrowValidation)
|
|
866
|
+
return OpenApi.validationError(c, {
|
|
867
|
+
code: 'query_invalid',
|
|
868
|
+
message: 'Invalid query parameters',
|
|
869
|
+
})
|
|
870
|
+
|
|
871
|
+
const { address } = c.req.valid('param')
|
|
872
|
+
const query = c.req.valid('query')
|
|
873
|
+
const chainId = query.chainId ?? c.get('chainId')
|
|
874
|
+
try {
|
|
875
|
+
const data = await listPositions(c, { ...query, address, chainId, oracle })
|
|
876
|
+
return c.json(Response.validated(schema.getEarnAddressPositions.Response, data), 200)
|
|
877
|
+
} catch (cause) {
|
|
878
|
+
if (cause instanceof Valuation.UnsupportedDenominationError)
|
|
879
|
+
return Response.error(c, { code: 'query_invalid', message: cause.message, status: 400 })
|
|
880
|
+
return Response.upstream(c, cause)
|
|
881
|
+
}
|
|
882
|
+
},
|
|
883
|
+
)
|
|
676
884
|
.get(
|
|
677
885
|
'/v1/earn/vaults/:vaultId/positions/:address',
|
|
678
886
|
Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }),
|
|
@@ -686,7 +894,7 @@ export function earn(options: earn.Options = {}) {
|
|
|
686
894
|
}),
|
|
687
895
|
OpenApi.describeRoute({
|
|
688
896
|
description:
|
|
689
|
-
'Gets an account’s asset and
|
|
897
|
+
'Gets an account’s asset and earn-share balances, allowances, and current asset value for one earn vault.',
|
|
690
898
|
operationId: 'getEarnVaultPosition',
|
|
691
899
|
responses: OpenApi.responses({
|
|
692
900
|
errors: {
|
|
@@ -700,12 +908,12 @@ export function earn(options: earn.Options = {}) {
|
|
|
700
908
|
},
|
|
701
909
|
404: {
|
|
702
910
|
codes: ['earn_vault_not_found'],
|
|
703
|
-
description: 'No compatible
|
|
911
|
+
description: 'No compatible earn vault was found at the supplied vault address.',
|
|
704
912
|
},
|
|
705
|
-
502: 'Could not read the
|
|
913
|
+
502: 'Could not read the earn vault position.',
|
|
706
914
|
},
|
|
707
915
|
success: {
|
|
708
|
-
description: 'Current
|
|
916
|
+
description: 'Current earn position for one account in one vault.',
|
|
709
917
|
schema: schema.getEarnVaultPosition.Response,
|
|
710
918
|
},
|
|
711
919
|
}),
|
|
@@ -791,7 +999,7 @@ export function earn(options: earn.Options = {}) {
|
|
|
791
999
|
}),
|
|
792
1000
|
OpenApi.describeRoute({
|
|
793
1001
|
description:
|
|
794
|
-
'Gets an account’s indexed asset value and earnings for lifetime history or shares still held.',
|
|
1002
|
+
'Gets an account’s indexed asset value and earnings for lifetime history, the trailing 30 days, or shares still held.',
|
|
795
1003
|
operationId: 'getEarnVaultEarnings',
|
|
796
1004
|
responses: OpenApi.responses({
|
|
797
1005
|
errors: {
|
|
@@ -805,9 +1013,9 @@ export function earn(options: earn.Options = {}) {
|
|
|
805
1013
|
},
|
|
806
1014
|
404: {
|
|
807
1015
|
codes: ['earn_vault_not_found'],
|
|
808
|
-
description: 'No compatible
|
|
1016
|
+
description: 'No compatible earn vault was found at this address.',
|
|
809
1017
|
},
|
|
810
|
-
502: 'Could not read the
|
|
1018
|
+
502: 'Could not read the earn vault earnings.',
|
|
811
1019
|
},
|
|
812
1020
|
success: {
|
|
813
1021
|
description: 'Cost-basis-aware earnings for one account in one vault.',
|
|
@@ -820,7 +1028,7 @@ export function earn(options: earn.Options = {}) {
|
|
|
820
1028
|
Cache.response<App.Environment>({
|
|
821
1029
|
cacheControl: Cache.policies.state,
|
|
822
1030
|
key: (c) => Cache.urlKey(c, schema.getEarnVaultEarnings.Query),
|
|
823
|
-
name: 'tempo-api:earn-vault-earnings:
|
|
1031
|
+
name: 'tempo-api:earn-vault-earnings:v3',
|
|
824
1032
|
}),
|
|
825
1033
|
async (c) => {
|
|
826
1034
|
if (Auth.narrowAccess) return Auth.paidAccessError(c)
|
|
@@ -886,18 +1094,18 @@ export function earn(options: earn.Options = {}) {
|
|
|
886
1094
|
}),
|
|
887
1095
|
OpenApi.describeRoute({
|
|
888
1096
|
description:
|
|
889
|
-
'Resolves any compatible
|
|
1097
|
+
'Resolves any compatible earn vault directly from current chain state and enriches it when curated.',
|
|
890
1098
|
operationId: 'getEarnVault',
|
|
891
1099
|
responses: OpenApi.responses({
|
|
892
1100
|
errors: {
|
|
893
1101
|
404: {
|
|
894
1102
|
codes: ['earn_vault_not_found'],
|
|
895
|
-
description: 'No compatible
|
|
1103
|
+
description: 'No compatible earn vault was found at this address.',
|
|
896
1104
|
},
|
|
897
|
-
502: 'Could not read
|
|
1105
|
+
502: 'Could not read earn vault data.',
|
|
898
1106
|
},
|
|
899
1107
|
success: {
|
|
900
|
-
description: 'One compatible
|
|
1108
|
+
description: 'One compatible earn vault.',
|
|
901
1109
|
schema: schema.getEarnVault.Response,
|
|
902
1110
|
},
|
|
903
1111
|
}),
|
|
@@ -962,7 +1170,17 @@ export async function getVaultEarnings(
|
|
|
962
1170
|
) {
|
|
963
1171
|
const client = c.get('getClient')(options.chainId)
|
|
964
1172
|
const tidx = c.get('getTidx')(options.chainId)
|
|
965
|
-
const
|
|
1173
|
+
const block = await getLatestIndexedBlock(tidx)
|
|
1174
|
+
const blockNumber = block.number
|
|
1175
|
+
if (options.period === '30d')
|
|
1176
|
+
return getWindowVaultEarnings(c, {
|
|
1177
|
+
account: options.account,
|
|
1178
|
+
blockNumber,
|
|
1179
|
+
chainId: options.chainId,
|
|
1180
|
+
period: options.period,
|
|
1181
|
+
timestamp: block.timestamp,
|
|
1182
|
+
vault: options.vault,
|
|
1183
|
+
})
|
|
966
1184
|
const position = client.earn.getPosition({
|
|
967
1185
|
account: options.account,
|
|
968
1186
|
blockNumber: BigInt(blockNumber),
|
|
@@ -995,6 +1213,7 @@ export async function getVaultEarnings(
|
|
|
995
1213
|
getVaultEarningsHistory(tidx, {
|
|
996
1214
|
account: options.account,
|
|
997
1215
|
blockNumber,
|
|
1216
|
+
startBlockNumber: beforeFirstBlockNumber,
|
|
998
1217
|
vault: options.vault,
|
|
999
1218
|
}),
|
|
1000
1219
|
position,
|
|
@@ -1003,6 +1222,7 @@ export async function getVaultEarnings(
|
|
|
1003
1222
|
account: options.account,
|
|
1004
1223
|
blockNumber,
|
|
1005
1224
|
shareToken: current.shareToken,
|
|
1225
|
+
startBlockNumber: beforeFirstBlockNumber,
|
|
1006
1226
|
vault: options.vault,
|
|
1007
1227
|
})
|
|
1008
1228
|
const base = {
|
|
@@ -1020,6 +1240,126 @@ export async function getVaultEarnings(
|
|
|
1020
1240
|
return { ...base, lifetimeEarnings: lifetimeEarnings.toString(), status: 'complete' as const }
|
|
1021
1241
|
}
|
|
1022
1242
|
|
|
1243
|
+
/** Reads cash-flow-adjusted earnings over a trailing indexed window. */
|
|
1244
|
+
async function getWindowVaultEarnings(
|
|
1245
|
+
c: Context<App.Environment>,
|
|
1246
|
+
options: getWindowVaultEarnings.Options,
|
|
1247
|
+
) {
|
|
1248
|
+
const client = c.get('getClient')(options.chainId)
|
|
1249
|
+
const tidx = c.get('getTidx')(options.chainId)
|
|
1250
|
+
const [current, startBlockNumber] = await Promise.all([
|
|
1251
|
+
client.earn.getPosition({
|
|
1252
|
+
account: options.account,
|
|
1253
|
+
blockNumber: BigInt(options.blockNumber),
|
|
1254
|
+
vault: options.vault,
|
|
1255
|
+
}),
|
|
1256
|
+
getWindowStartBlockNumber(tidx, { days: 30, timestamp: options.timestamp }),
|
|
1257
|
+
])
|
|
1258
|
+
const [externalShareTransfer, history, opening] = await Promise.all([
|
|
1259
|
+
hasExternalShareTransfer(tidx, {
|
|
1260
|
+
account: options.account,
|
|
1261
|
+
blockNumber: options.blockNumber,
|
|
1262
|
+
shareToken: current.shareToken,
|
|
1263
|
+
startBlockNumber,
|
|
1264
|
+
vault: options.vault,
|
|
1265
|
+
}),
|
|
1266
|
+
getVaultEarningsHistory(tidx, {
|
|
1267
|
+
account: options.account,
|
|
1268
|
+
blockNumber: options.blockNumber,
|
|
1269
|
+
startBlockNumber,
|
|
1270
|
+
vault: options.vault,
|
|
1271
|
+
}),
|
|
1272
|
+
getWindowShareState(tidx, {
|
|
1273
|
+
account: options.account,
|
|
1274
|
+
blockNumber: options.blockNumber,
|
|
1275
|
+
shareToken: current.shareToken,
|
|
1276
|
+
startBlockNumber,
|
|
1277
|
+
vault: options.vault,
|
|
1278
|
+
}),
|
|
1279
|
+
])
|
|
1280
|
+
const base = {
|
|
1281
|
+
account: options.account,
|
|
1282
|
+
assetToken: current.assetToken,
|
|
1283
|
+
currentValue: current.value.toString(),
|
|
1284
|
+
id: options.account,
|
|
1285
|
+
period: options.period,
|
|
1286
|
+
}
|
|
1287
|
+
if (
|
|
1288
|
+
opening === undefined ||
|
|
1289
|
+
externalShareTransfer ||
|
|
1290
|
+
history.incompleteCostBasis ||
|
|
1291
|
+
opening.crossBoundaryRedemption ||
|
|
1292
|
+
opening.endShares !== current.shareBalance
|
|
1293
|
+
)
|
|
1294
|
+
return { ...base, status: 'incomplete_cost_basis' as const }
|
|
1295
|
+
if (history.pendingRedemption) return { ...base, status: 'pending_redemption' as const }
|
|
1296
|
+
const openingValue =
|
|
1297
|
+
opening.startShares === 0n
|
|
1298
|
+
? 0n
|
|
1299
|
+
: await client.earn.getRedeemQuote({
|
|
1300
|
+
blockNumber: BigInt(startBlockNumber),
|
|
1301
|
+
shareAmount: opening.startShares,
|
|
1302
|
+
vault: options.vault,
|
|
1303
|
+
})
|
|
1304
|
+
const windowEarnings = calculateWindowEarnings({
|
|
1305
|
+
deposited: history.deposited,
|
|
1306
|
+
endingValue: current.value,
|
|
1307
|
+
finalized: history.finalized,
|
|
1308
|
+
openingValue,
|
|
1309
|
+
redeemed: history.redeemed,
|
|
1310
|
+
withdrewExact: history.withdrewExact,
|
|
1311
|
+
})
|
|
1312
|
+
return { ...base, status: 'complete' as const, windowEarnings: windowEarnings.toString() }
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
declare namespace getWindowVaultEarnings {
|
|
1316
|
+
/** Inputs identifying one account, vault, and indexed window end. */
|
|
1317
|
+
type Options = {
|
|
1318
|
+
/** Account whose earnings are calculated. */
|
|
1319
|
+
account: z.output<typeof Schema.Address>
|
|
1320
|
+
/** Latest indexed block number. */
|
|
1321
|
+
blockNumber: number
|
|
1322
|
+
/** Tempo chain containing the vault. */
|
|
1323
|
+
chainId: z.output<typeof Schema.ChainId>
|
|
1324
|
+
/** Trailing earnings period. */
|
|
1325
|
+
period: '30d'
|
|
1326
|
+
/** Latest indexed block timestamp. */
|
|
1327
|
+
timestamp: string
|
|
1328
|
+
/** Earn vault contract address. */
|
|
1329
|
+
vault: z.output<typeof Schema.Address>
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
/** Calculates cash-flow-adjusted earnings between two position snapshots. */
|
|
1334
|
+
export function calculateWindowEarnings(options: calculateWindowEarnings.Options) {
|
|
1335
|
+
return (
|
|
1336
|
+
options.endingValue +
|
|
1337
|
+
options.redeemed +
|
|
1338
|
+
options.withdrewExact +
|
|
1339
|
+
options.finalized -
|
|
1340
|
+
options.deposited -
|
|
1341
|
+
options.openingValue
|
|
1342
|
+
)
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
export declare namespace calculateWindowEarnings {
|
|
1346
|
+
/** Asset-denominated values spanning one earnings window. */
|
|
1347
|
+
type Options = {
|
|
1348
|
+
/** Assets deposited during the window. */
|
|
1349
|
+
deposited: bigint
|
|
1350
|
+
/** Asset value of shares held at the ending boundary. */
|
|
1351
|
+
endingValue: bigint
|
|
1352
|
+
/** Assets received from async redemptions finalized during the window. */
|
|
1353
|
+
finalized: bigint
|
|
1354
|
+
/** Asset value of shares held at the opening boundary. */
|
|
1355
|
+
openingValue: bigint
|
|
1356
|
+
/** Assets received from redemptions during the window. */
|
|
1357
|
+
redeemed: bigint
|
|
1358
|
+
/** Assets received from exact withdrawals during the window. */
|
|
1359
|
+
withdrewExact: bigint
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1023
1363
|
export declare namespace getVaultEarnings {
|
|
1024
1364
|
/** Inputs identifying the account and vault to calculate. */
|
|
1025
1365
|
type Options = {
|
|
@@ -1031,16 +1371,45 @@ export declare namespace getVaultEarnings {
|
|
|
1031
1371
|
}
|
|
1032
1372
|
|
|
1033
1373
|
/** Reads the latest block whose indexed logs back the earnings calculation. */
|
|
1034
|
-
async function
|
|
1374
|
+
async function getLatestIndexedBlock(tidx: Tidx.Client) {
|
|
1035
1375
|
const result = await tidx.fetch({
|
|
1036
1376
|
engine: 'clickhouse',
|
|
1037
|
-
query: 'SELECT num FROM blocks ORDER BY num DESC LIMIT 1' as string,
|
|
1377
|
+
query: 'SELECT num, timestamp FROM blocks ORDER BY num DESC LIMIT 1' as string,
|
|
1378
|
+
})
|
|
1379
|
+
const number = Value.toNumber(result.rows[0]?.['num'])
|
|
1380
|
+
const timestamp = Value.toIsoDateTime(result.rows[0]?.['timestamp'])
|
|
1381
|
+
if (number === undefined || timestamp === undefined)
|
|
1382
|
+
throw new Error('TIDX returned no indexed block.')
|
|
1383
|
+
return { number, timestamp }
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
/** Resolves the last indexed block at or before a trailing-day boundary. */
|
|
1387
|
+
async function getWindowStartBlockNumber(
|
|
1388
|
+
tidx: Tidx.Client,
|
|
1389
|
+
options: getWindowStartBlockNumber.Options,
|
|
1390
|
+
) {
|
|
1391
|
+
const cutoff = new Date(Date.parse(options.timestamp) - Ttl.days(options.days)).toISOString()
|
|
1392
|
+
const result = await tidx.fetch({
|
|
1393
|
+
query:
|
|
1394
|
+
`SELECT num FROM blocks WHERE timestamp <= '${cutoff}' ORDER BY num DESC LIMIT 1` as string,
|
|
1038
1395
|
})
|
|
1039
|
-
const
|
|
1040
|
-
if (
|
|
1396
|
+
const row = result.rows[0]
|
|
1397
|
+
if (!row) return beforeFirstBlockNumber
|
|
1398
|
+
const blockNumber = Value.toNumber(row['num'])
|
|
1399
|
+
if (blockNumber === undefined) throw new Error('TIDX returned no Earn earnings boundary.')
|
|
1041
1400
|
return blockNumber
|
|
1042
1401
|
}
|
|
1043
1402
|
|
|
1403
|
+
declare namespace getWindowStartBlockNumber {
|
|
1404
|
+
/** Inputs defining a trailing boundary from an indexed timestamp. */
|
|
1405
|
+
type Options = {
|
|
1406
|
+
/** Number of trailing days. */
|
|
1407
|
+
days: number
|
|
1408
|
+
/** Timestamp anchoring the end of the window. */
|
|
1409
|
+
timestamp: string
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1044
1413
|
/** Reads the indexed cash flows and exceptional states behind one earnings calculation. */
|
|
1045
1414
|
async function getVaultEarningsHistory(
|
|
1046
1415
|
tidx: Tidx.Client,
|
|
@@ -1055,6 +1424,7 @@ async function getVaultEarningsHistory(
|
|
|
1055
1424
|
FROM Deposited
|
|
1056
1425
|
WHERE address = '${options.vault}'
|
|
1057
1426
|
AND receiver = '${options.account}'
|
|
1427
|
+
AND block_num > ${options.startBlockNumber}
|
|
1058
1428
|
AND block_num <= ${options.blockNumber}
|
|
1059
1429
|
) AS deposited_assets,
|
|
1060
1430
|
(
|
|
@@ -1065,6 +1435,7 @@ async function getVaultEarningsHistory(
|
|
|
1065
1435
|
AND requested."requestId" = finalized."requestId"
|
|
1066
1436
|
WHERE finalized.address = '${options.vault}'
|
|
1067
1437
|
AND requested.requester = '${options.account}'
|
|
1438
|
+
AND finalized.block_num > ${options.startBlockNumber}
|
|
1068
1439
|
AND finalized.block_num <= ${options.blockNumber}
|
|
1069
1440
|
AND requested.block_num <= ${options.blockNumber}
|
|
1070
1441
|
) AS finalized_assets,
|
|
@@ -1088,6 +1459,7 @@ async function getVaultEarningsHistory(
|
|
|
1088
1459
|
FROM Redeemed
|
|
1089
1460
|
WHERE address = '${options.vault}'
|
|
1090
1461
|
AND caller = '${options.account}'
|
|
1462
|
+
AND block_num > ${options.startBlockNumber}
|
|
1091
1463
|
AND block_num <= ${options.blockNumber}
|
|
1092
1464
|
) AS redeemed_assets,
|
|
1093
1465
|
(
|
|
@@ -1097,6 +1469,7 @@ async function getVaultEarningsHistory(
|
|
|
1097
1469
|
ON requested.address = cancelled.address
|
|
1098
1470
|
AND requested."requestId" = cancelled."requestId"
|
|
1099
1471
|
WHERE cancelled.address = '${options.vault}'
|
|
1472
|
+
AND cancelled.block_num > ${options.startBlockNumber}
|
|
1100
1473
|
AND cancelled.block_num <= ${options.blockNumber}
|
|
1101
1474
|
AND requested.block_num <= ${options.blockNumber}
|
|
1102
1475
|
AND requested.requester != cancelled.receiver
|
|
@@ -1110,6 +1483,7 @@ async function getVaultEarningsHistory(
|
|
|
1110
1483
|
FROM VenueSharesDeposited
|
|
1111
1484
|
WHERE address = '${options.vault}'
|
|
1112
1485
|
AND receiver = '${options.account}'
|
|
1486
|
+
AND block_num > ${options.startBlockNumber}
|
|
1113
1487
|
AND block_num <= ${options.blockNumber}
|
|
1114
1488
|
) AS venue_deposits,
|
|
1115
1489
|
(
|
|
@@ -1117,6 +1491,7 @@ async function getVaultEarningsHistory(
|
|
|
1117
1491
|
FROM WithdrewExact
|
|
1118
1492
|
WHERE address = '${options.vault}'
|
|
1119
1493
|
AND caller = '${options.account}'
|
|
1494
|
+
AND block_num > ${options.startBlockNumber}
|
|
1120
1495
|
AND block_num <= ${options.blockNumber}
|
|
1121
1496
|
) AS withdrew_exact
|
|
1122
1497
|
` as string,
|
|
@@ -1162,6 +1537,112 @@ declare namespace getVaultEarningsHistory {
|
|
|
1162
1537
|
type Options = {
|
|
1163
1538
|
account: z.output<typeof Schema.Address>
|
|
1164
1539
|
blockNumber: number
|
|
1540
|
+
startBlockNumber: number
|
|
1541
|
+
vault: z.output<typeof Schema.Address>
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
/** Reads share balances and cross-boundary redemptions for a trailing window. */
|
|
1546
|
+
async function getWindowShareState(tidx: Tidx.Client, options: getWindowShareState.Options) {
|
|
1547
|
+
const result = await tidx.fetch({
|
|
1548
|
+
engine: 'clickhouse',
|
|
1549
|
+
query: `
|
|
1550
|
+
SELECT
|
|
1551
|
+
(
|
|
1552
|
+
SELECT toString(sum(toUInt256(amount)))
|
|
1553
|
+
FROM token_transfers
|
|
1554
|
+
WHERE token = '${options.shareToken.toLowerCase()}'
|
|
1555
|
+
AND "to" = '${options.account}'
|
|
1556
|
+
AND block_num <= ${options.startBlockNumber}
|
|
1557
|
+
) AS received_start,
|
|
1558
|
+
(
|
|
1559
|
+
SELECT toString(sum(toUInt256(amount)))
|
|
1560
|
+
FROM token_transfers
|
|
1561
|
+
WHERE token = '${options.shareToken.toLowerCase()}'
|
|
1562
|
+
AND "from" = '${options.account}'
|
|
1563
|
+
AND block_num <= ${options.startBlockNumber}
|
|
1564
|
+
) AS sent_start,
|
|
1565
|
+
(
|
|
1566
|
+
SELECT toString(sum(toUInt256(amount)))
|
|
1567
|
+
FROM token_transfers
|
|
1568
|
+
WHERE token = '${options.shareToken.toLowerCase()}'
|
|
1569
|
+
AND "to" = '${options.account}'
|
|
1570
|
+
AND block_num <= ${options.blockNumber}
|
|
1571
|
+
) AS received_end,
|
|
1572
|
+
(
|
|
1573
|
+
SELECT toString(sum(toUInt256(amount)))
|
|
1574
|
+
FROM token_transfers
|
|
1575
|
+
WHERE token = '${options.shareToken.toLowerCase()}'
|
|
1576
|
+
AND "from" = '${options.account}'
|
|
1577
|
+
AND block_num <= ${options.blockNumber}
|
|
1578
|
+
) AS sent_end,
|
|
1579
|
+
(
|
|
1580
|
+
SELECT count()
|
|
1581
|
+
FROM (
|
|
1582
|
+
SELECT finalized."requestId"
|
|
1583
|
+
FROM RedeemFinalized AS finalized
|
|
1584
|
+
INNER JOIN RedeemRequested AS requested
|
|
1585
|
+
ON requested.address = finalized.address
|
|
1586
|
+
AND requested."requestId" = finalized."requestId"
|
|
1587
|
+
WHERE finalized.address = '${options.vault}'
|
|
1588
|
+
AND requested.requester = '${options.account}'
|
|
1589
|
+
AND requested.block_num <= ${options.startBlockNumber}
|
|
1590
|
+
AND finalized.block_num > ${options.startBlockNumber}
|
|
1591
|
+
AND finalized.block_num <= ${options.blockNumber}
|
|
1592
|
+
UNION ALL
|
|
1593
|
+
SELECT cancelled."requestId"
|
|
1594
|
+
FROM RedeemCancelled AS cancelled
|
|
1595
|
+
INNER JOIN RedeemRequested AS requested
|
|
1596
|
+
ON requested.address = cancelled.address
|
|
1597
|
+
AND requested."requestId" = cancelled."requestId"
|
|
1598
|
+
WHERE cancelled.address = '${options.vault}'
|
|
1599
|
+
AND requested.requester = '${options.account}'
|
|
1600
|
+
AND requested.block_num <= ${options.startBlockNumber}
|
|
1601
|
+
AND cancelled.block_num > ${options.startBlockNumber}
|
|
1602
|
+
AND cancelled.block_num <= ${options.blockNumber}
|
|
1603
|
+
) AS cross_boundary
|
|
1604
|
+
) AS cross_boundary_redemptions
|
|
1605
|
+
` as string,
|
|
1606
|
+
signatures: [redeemCancelledSignature, redeemFinalizedSignature, redeemRequestedSignature],
|
|
1607
|
+
})
|
|
1608
|
+
const row = result.rows[0]
|
|
1609
|
+
const crossBoundaryRedemptions = Value.toNumber(row?.['cross_boundary_redemptions'])
|
|
1610
|
+
const receivedEnd = Value.toIntegerString(row?.['received_end'])
|
|
1611
|
+
const receivedStart = Value.toIntegerString(row?.['received_start'])
|
|
1612
|
+
const sentEnd = Value.toIntegerString(row?.['sent_end'])
|
|
1613
|
+
const sentStart = Value.toIntegerString(row?.['sent_start'])
|
|
1614
|
+
if (
|
|
1615
|
+
crossBoundaryRedemptions === undefined ||
|
|
1616
|
+
receivedEnd === undefined ||
|
|
1617
|
+
receivedStart === undefined ||
|
|
1618
|
+
sentEnd === undefined ||
|
|
1619
|
+
sentStart === undefined
|
|
1620
|
+
)
|
|
1621
|
+
return undefined
|
|
1622
|
+
const receivedEndValue = BigInt(receivedEnd)
|
|
1623
|
+
const receivedStartValue = BigInt(receivedStart)
|
|
1624
|
+
const sentEndValue = BigInt(sentEnd)
|
|
1625
|
+
const sentStartValue = BigInt(sentStart)
|
|
1626
|
+
if (sentEndValue > receivedEndValue || sentStartValue > receivedStartValue) return undefined
|
|
1627
|
+
return {
|
|
1628
|
+
crossBoundaryRedemption: crossBoundaryRedemptions > 0,
|
|
1629
|
+
endShares: receivedEndValue - sentEndValue,
|
|
1630
|
+
startShares: receivedStartValue - sentStartValue,
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
declare namespace getWindowShareState {
|
|
1635
|
+
/** Inputs identifying one account's share history window. */
|
|
1636
|
+
type Options = {
|
|
1637
|
+
/** Account whose share history is read. */
|
|
1638
|
+
account: z.output<typeof Schema.Address>
|
|
1639
|
+
/** Ending indexed block number. */
|
|
1640
|
+
blockNumber: number
|
|
1641
|
+
/** Earn share token contract address. */
|
|
1642
|
+
shareToken: z.output<typeof Schema.Address>
|
|
1643
|
+
/** Opening indexed block number. */
|
|
1644
|
+
startBlockNumber: number
|
|
1645
|
+
/** Earn vault contract address. */
|
|
1165
1646
|
vault: z.output<typeof Schema.Address>
|
|
1166
1647
|
}
|
|
1167
1648
|
}
|
|
@@ -1180,10 +1661,15 @@ async function hasExternalShareTransfer(
|
|
|
1180
1661
|
SELECT count()
|
|
1181
1662
|
FROM token_transfers
|
|
1182
1663
|
WHERE token = '${options.shareToken.toLowerCase()}'
|
|
1664
|
+
AND block_num > ${options.startBlockNumber}
|
|
1183
1665
|
AND block_num <= ${options.blockNumber}
|
|
1184
1666
|
AND (
|
|
1185
1667
|
("from" = '${options.account}' AND "to" != '${options.vault}')
|
|
1186
|
-
OR (
|
|
1668
|
+
OR (
|
|
1669
|
+
"to" = '${options.account}'
|
|
1670
|
+
AND "from" != '${zeroAddress}'
|
|
1671
|
+
AND "from" != '${options.vault}'
|
|
1672
|
+
)
|
|
1187
1673
|
)
|
|
1188
1674
|
) + (
|
|
1189
1675
|
SELECT count()
|
|
@@ -1193,6 +1679,7 @@ async function hasExternalShareTransfer(
|
|
|
1193
1679
|
WHERE token = '${options.shareToken.toLowerCase()}'
|
|
1194
1680
|
AND "from" = '${options.account}'
|
|
1195
1681
|
AND "to" = '${options.vault}'
|
|
1682
|
+
AND block_num > ${options.startBlockNumber}
|
|
1196
1683
|
AND block_num <= ${options.blockNumber}
|
|
1197
1684
|
GROUP BY tx_hash, amount
|
|
1198
1685
|
) AS candidate
|
|
@@ -1203,27 +1690,58 @@ async function hasExternalShareTransfer(
|
|
|
1203
1690
|
FROM Redeemed
|
|
1204
1691
|
WHERE address = '${options.vault}'
|
|
1205
1692
|
AND caller = '${options.account}'
|
|
1693
|
+
AND block_num > ${options.startBlockNumber}
|
|
1206
1694
|
AND block_num <= ${options.blockNumber}
|
|
1207
1695
|
UNION ALL
|
|
1208
1696
|
SELECT tx_hash, toUInt256(earnShares) AS amount
|
|
1209
1697
|
FROM RedeemRequested
|
|
1210
1698
|
WHERE address = '${options.vault}'
|
|
1211
1699
|
AND requester = '${options.account}'
|
|
1700
|
+
AND block_num > ${options.startBlockNumber}
|
|
1212
1701
|
AND block_num <= ${options.blockNumber}
|
|
1213
1702
|
UNION ALL
|
|
1214
1703
|
SELECT tx_hash, toUInt256(earnSharesBurned) AS amount
|
|
1215
1704
|
FROM WithdrewExact
|
|
1216
1705
|
WHERE address = '${options.vault}'
|
|
1217
1706
|
AND caller = '${options.account}'
|
|
1707
|
+
AND block_num > ${options.startBlockNumber}
|
|
1218
1708
|
AND block_num <= ${options.blockNumber}
|
|
1219
1709
|
) AS protocol_exit
|
|
1220
1710
|
GROUP BY tx_hash, amount
|
|
1221
1711
|
) AS matched
|
|
1222
1712
|
ON matched.tx_hash = candidate.tx_hash AND matched.amount = candidate.amount
|
|
1223
1713
|
WHERE candidate.transfer_count > coalesce(matched.exit_count, 0)
|
|
1714
|
+
) + (
|
|
1715
|
+
SELECT count()
|
|
1716
|
+
FROM (
|
|
1717
|
+
SELECT tx_hash, toUInt256(amount) AS amount, count() AS transfer_count
|
|
1718
|
+
FROM token_transfers
|
|
1719
|
+
WHERE token = '${options.shareToken.toLowerCase()}'
|
|
1720
|
+
AND "from" = '${options.vault}'
|
|
1721
|
+
AND "to" = '${options.account}'
|
|
1722
|
+
AND block_num > ${options.startBlockNumber}
|
|
1723
|
+
AND block_num <= ${options.blockNumber}
|
|
1724
|
+
GROUP BY tx_hash, amount
|
|
1725
|
+
) AS candidate
|
|
1726
|
+
LEFT JOIN (
|
|
1727
|
+
SELECT tx_hash, toUInt256(earnShares) AS amount, count() AS cancel_count
|
|
1728
|
+
FROM RedeemCancelled
|
|
1729
|
+
WHERE address = '${options.vault}'
|
|
1730
|
+
AND receiver = '${options.account}'
|
|
1731
|
+
AND block_num > ${options.startBlockNumber}
|
|
1732
|
+
AND block_num <= ${options.blockNumber}
|
|
1733
|
+
GROUP BY tx_hash, amount
|
|
1734
|
+
) AS matched
|
|
1735
|
+
ON matched.tx_hash = candidate.tx_hash AND matched.amount = candidate.amount
|
|
1736
|
+
WHERE candidate.transfer_count > coalesce(matched.cancel_count, 0)
|
|
1224
1737
|
) AS count
|
|
1225
1738
|
` as string,
|
|
1226
|
-
signatures: [
|
|
1739
|
+
signatures: [
|
|
1740
|
+
redeemCancelledSignature,
|
|
1741
|
+
redeemedSignature,
|
|
1742
|
+
redeemRequestedSignature,
|
|
1743
|
+
withdrewExactSignature,
|
|
1744
|
+
],
|
|
1227
1745
|
})
|
|
1228
1746
|
const count = Value.toNumber(result.rows[0]?.['count'])
|
|
1229
1747
|
if (count === undefined) throw new Error('TIDX returned an invalid Earn-share transfer count.')
|
|
@@ -1235,6 +1753,7 @@ declare namespace hasExternalShareTransfer {
|
|
|
1235
1753
|
account: z.output<typeof Schema.Address>
|
|
1236
1754
|
blockNumber: number
|
|
1237
1755
|
shareToken: z.output<typeof Schema.Address>
|
|
1756
|
+
startBlockNumber: number
|
|
1238
1757
|
vault: z.output<typeof Schema.Address>
|
|
1239
1758
|
}
|
|
1240
1759
|
}
|
|
@@ -1699,6 +2218,270 @@ export declare namespace listVerifiedVaults {
|
|
|
1699
2218
|
}
|
|
1700
2219
|
}
|
|
1701
2220
|
|
|
2221
|
+
/** Lists every vault where an account holds earn shares, ordered by vault address. */
|
|
2222
|
+
export async function listPositions(c: Context<App.Environment>, options: listPositions.Options) {
|
|
2223
|
+
const denomination = options['valuation.currency']
|
|
2224
|
+
const db = Db.get(c.get('dbCached'))
|
|
2225
|
+
const recordsPromise = core_EarnVaults.list(db, { chainId: options.chainId })
|
|
2226
|
+
// The verified page needs no index: the registry is the candidate set.
|
|
2227
|
+
const heldPromise = options.verified
|
|
2228
|
+
? undefined
|
|
2229
|
+
: queryHeldVaults(c, { address: options.address, chainId: options.chainId })
|
|
2230
|
+
// Observing these branches prevents unhandled rejections while the
|
|
2231
|
+
// denomination validates; the later await still propagates them.
|
|
2232
|
+
void recordsPromise.catch(() => undefined)
|
|
2233
|
+
void heldPromise?.catch(() => undefined)
|
|
2234
|
+
// Validation resolves before discovery results are consumed, so a dependency
|
|
2235
|
+
// failure cannot mask a caller error.
|
|
2236
|
+
if (denomination)
|
|
2237
|
+
await Valuation.ratesFor(c, { currencies: [], denomination, oracle: options.oracle })
|
|
2238
|
+
const [records, held] = await Promise.all([recordsPromise, heldPromise])
|
|
2239
|
+
const registry = new Map(records.map((record) => [record.vaultAddress.toLowerCase(), record]))
|
|
2240
|
+
const vaults = held ?? [...registry.keys()].sort()
|
|
2241
|
+
const decoded = options.cursor ? Cursor.decode(options.cursor, ['address']) : undefined
|
|
2242
|
+
const cursor = decoded?.[0]?.toString().toLowerCase()
|
|
2243
|
+
const candidates = vaults.filter((vault) => !cursor || vault > cursor)
|
|
2244
|
+
const matches: listPositions.Match[] = []
|
|
2245
|
+
|
|
2246
|
+
let offset = 0
|
|
2247
|
+
// The deployment-scan bound also caps per-request position reads, so a long
|
|
2248
|
+
// exited-position history pages through candidates instead of probing them all.
|
|
2249
|
+
while (
|
|
2250
|
+
offset < candidates.length &&
|
|
2251
|
+
matches.length <= options.limit &&
|
|
2252
|
+
offset < maxScannedDeployments
|
|
2253
|
+
) {
|
|
2254
|
+
const batchSize = discoveryBatchLimit({
|
|
2255
|
+
// Zero-share candidates are read and then rejected, so batches over-fetch.
|
|
2256
|
+
filtered: true,
|
|
2257
|
+
limit: options.limit,
|
|
2258
|
+
matches: matches.length,
|
|
2259
|
+
remaining: Math.min(candidates.length, maxScannedDeployments) - offset,
|
|
2260
|
+
})
|
|
2261
|
+
const batch = candidates.slice(offset, offset + batchSize)
|
|
2262
|
+
offset += batch.length
|
|
2263
|
+
const read = await Timing.time(c, 'earn_positions', () =>
|
|
2264
|
+
Promise.all(
|
|
2265
|
+
batch.map(async (vault) => {
|
|
2266
|
+
const record = registry.get(vault)
|
|
2267
|
+
try {
|
|
2268
|
+
const position = await readPosition(c, {
|
|
2269
|
+
address: options.address,
|
|
2270
|
+
chainId: options.chainId,
|
|
2271
|
+
vaultAddress: Schema.Address.parse(vault),
|
|
2272
|
+
})
|
|
2273
|
+
return { position, record, vaultAddress: vault }
|
|
2274
|
+
} catch (cause) {
|
|
2275
|
+
if (!record && isIncompatible(cause)) return undefined
|
|
2276
|
+
throw cause
|
|
2277
|
+
}
|
|
2278
|
+
}),
|
|
2279
|
+
),
|
|
2280
|
+
)
|
|
2281
|
+
for (const match of read) {
|
|
2282
|
+
if (!match || match.position.shareBalance === 0n) continue
|
|
2283
|
+
matches.push(match)
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
|
|
2287
|
+
const page = matches.slice(0, options.limit)
|
|
2288
|
+
const [tokens, snapshot] = await Promise.all([
|
|
2289
|
+
(async () => {
|
|
2290
|
+
const addresses = [
|
|
2291
|
+
...new Set(page.flatMap(({ position }) => [position.assetToken, position.shareToken])),
|
|
2292
|
+
]
|
|
2293
|
+
const groups: Tokens.resolveTokens.ReturnType[] = []
|
|
2294
|
+
for (let index = 0; index < addresses.length; index += tokenReadChunkSize)
|
|
2295
|
+
groups.push(
|
|
2296
|
+
await Tokens.resolveTokens(c, {
|
|
2297
|
+
addresses: addresses.slice(index, index + tokenReadChunkSize),
|
|
2298
|
+
chainId: options.chainId,
|
|
2299
|
+
include: ['token.verified'],
|
|
2300
|
+
}),
|
|
2301
|
+
)
|
|
2302
|
+
return new Map(groups.flatMap((group) => [...group]))
|
|
2303
|
+
})(),
|
|
2304
|
+
denomination ? VerifiedTokens.snapshot(c, options.chainId) : undefined,
|
|
2305
|
+
])
|
|
2306
|
+
const rates =
|
|
2307
|
+
denomination && snapshot
|
|
2308
|
+
? await Valuation.ratesFor(c, {
|
|
2309
|
+
currencies: page.flatMap(({ position }) => {
|
|
2310
|
+
const currency = snapshot.byAddress.get(position.assetToken.toLowerCase())?.currency
|
|
2311
|
+
return currency === undefined ? [] : [currency]
|
|
2312
|
+
}),
|
|
2313
|
+
denomination,
|
|
2314
|
+
oracle: options.oracle,
|
|
2315
|
+
})
|
|
2316
|
+
: undefined
|
|
2317
|
+
const last = matches[options.limit - 1]
|
|
2318
|
+
// A scan-bound exit resumes from the last probed candidate so a truncated
|
|
2319
|
+
// page never hides positions deeper in the candidate list.
|
|
2320
|
+
const next =
|
|
2321
|
+
matches.length > options.limit
|
|
2322
|
+
? last?.vaultAddress
|
|
2323
|
+
: offset < candidates.length
|
|
2324
|
+
? candidates[offset - 1]
|
|
2325
|
+
: undefined
|
|
2326
|
+
return {
|
|
2327
|
+
data: page.map((match) =>
|
|
2328
|
+
serializePosition({
|
|
2329
|
+
denomination,
|
|
2330
|
+
position: match.position,
|
|
2331
|
+
rates,
|
|
2332
|
+
snapshot,
|
|
2333
|
+
tokens,
|
|
2334
|
+
vaultAddress: match.vaultAddress,
|
|
2335
|
+
verified: match.record !== undefined,
|
|
2336
|
+
}),
|
|
2337
|
+
),
|
|
2338
|
+
...(rates ? { meta: { valuation: Valuation.pricing(rates, options.oracle) } } : {}),
|
|
2339
|
+
nextCursor: next ? Cursor.encode([next]) : null,
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
export declare namespace listPositions {
|
|
2344
|
+
/** One candidate vault holding a non-zero position. */
|
|
2345
|
+
type Match = {
|
|
2346
|
+
position: Actions.earn.getPosition.ReturnValue
|
|
2347
|
+
record?: RegistryRecord | undefined
|
|
2348
|
+
vaultAddress: string
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
/** Options for listing an account's earn positions. */
|
|
2352
|
+
type Options = z.output<typeof schema.getEarnAddressPositions.Query> & {
|
|
2353
|
+
/** Account whose positions to list. */
|
|
2354
|
+
address: z.output<typeof Schema.Address>
|
|
2355
|
+
chainId: z.output<typeof Schema.ChainId>
|
|
2356
|
+
/** FX rate oracle backing per-row valuation. */
|
|
2357
|
+
oracle: FxOracle.Oracle
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
/** Finds vaults whose earn share token the account has ever held, by vault address. */
|
|
2362
|
+
async function queryHeldVaults(
|
|
2363
|
+
c: Context<App.Environment>,
|
|
2364
|
+
options: queryHeldVaults.Options,
|
|
2365
|
+
): Promise<readonly string[]> {
|
|
2366
|
+
const tidx = c.get('getTidx')(options.chainId)
|
|
2367
|
+
// Each vault's newest deployment binds its current share token; joining it
|
|
2368
|
+
// against the account's holder deltas server-side turns holdings into vault
|
|
2369
|
+
// candidates in one round trip, with no literal list to overflow the request.
|
|
2370
|
+
// Earn shares are factory-minted TIP-20s, so the `0x20c0` prefix bounds the
|
|
2371
|
+
// subquery. Stale delta rows are fine: the live read drops them.
|
|
2372
|
+
const result = await Timing.time(c, 'earn_positions_discovery', () =>
|
|
2373
|
+
tidx.fetch({
|
|
2374
|
+
chainId: options.chainId,
|
|
2375
|
+
engine: 'clickhouse',
|
|
2376
|
+
query: `
|
|
2377
|
+
SELECT "earnVault"
|
|
2378
|
+
FROM (
|
|
2379
|
+
SELECT
|
|
2380
|
+
"earnVault",
|
|
2381
|
+
"earnShare",
|
|
2382
|
+
row_number() OVER (
|
|
2383
|
+
PARTITION BY lower("earnVault")
|
|
2384
|
+
ORDER BY block_num DESC, log_idx DESC
|
|
2385
|
+
) AS deployment_rank
|
|
2386
|
+
FROM EarnStackDeployed
|
|
2387
|
+
)
|
|
2388
|
+
WHERE deployment_rank = 1 AND lower("earnShare") IN (
|
|
2389
|
+
SELECT DISTINCT lower(token)
|
|
2390
|
+
FROM address_holder_deltas
|
|
2391
|
+
WHERE holder = '${options.address}' AND startsWith(token, '0x20c0')
|
|
2392
|
+
)
|
|
2393
|
+
ORDER BY lower("earnVault") ASC
|
|
2394
|
+
` as string,
|
|
2395
|
+
signatures: [deployedSignature],
|
|
2396
|
+
}),
|
|
2397
|
+
)
|
|
2398
|
+
const rows: readonly Record<string, unknown>[] = result.rows
|
|
2399
|
+
return rows.flatMap((row) => {
|
|
2400
|
+
const vault = Schema.Address.safeParse(Value.toText(row['earnVault']))
|
|
2401
|
+
return vault.success ? [vault.data.toLowerCase()] : []
|
|
2402
|
+
})
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
declare namespace queryHeldVaults {
|
|
2406
|
+
/** Inputs selecting an account's held earn share tokens. */
|
|
2407
|
+
type Options = {
|
|
2408
|
+
address: z.output<typeof Schema.Address>
|
|
2409
|
+
chainId: z.output<typeof Schema.ChainId>
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
/** Reads one account's live position in one vault. */
|
|
2414
|
+
function readPosition(
|
|
2415
|
+
c: Context<App.Environment>,
|
|
2416
|
+
options: readPosition.Options,
|
|
2417
|
+
): Promise<Actions.earn.getPosition.ReturnValue> {
|
|
2418
|
+
return c
|
|
2419
|
+
.get('getClient')(options.chainId)
|
|
2420
|
+
.earn.getPosition({ account: options.address, vault: options.vaultAddress })
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
declare namespace readPosition {
|
|
2424
|
+
/** Inputs selecting one vault position. */
|
|
2425
|
+
type Options = {
|
|
2426
|
+
address: z.output<typeof Schema.Address>
|
|
2427
|
+
chainId: z.output<typeof Schema.ChainId>
|
|
2428
|
+
vaultAddress: z.output<typeof Schema.Address>
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
/** Serializes one live position into the address-scoped row contract. */
|
|
2433
|
+
function serializePosition(options: serializePosition.Options) {
|
|
2434
|
+
const { position, tokens } = options
|
|
2435
|
+
const assetToken = getToken(tokens, position.assetToken)
|
|
2436
|
+
const shareToken = getToken(tokens, position.shareToken)
|
|
2437
|
+
const vaultAddress = Schema.Address.parse(options.vaultAddress.toLowerCase())
|
|
2438
|
+
return {
|
|
2439
|
+
assetAmount: valueFor(position.value, assetToken),
|
|
2440
|
+
assetToken,
|
|
2441
|
+
id: vaultAddress,
|
|
2442
|
+
shareAmount: valueFor(position.shareBalance, shareToken),
|
|
2443
|
+
shareToken,
|
|
2444
|
+
...(options.denomination
|
|
2445
|
+
? {
|
|
2446
|
+
valuation: Valuation.valuationFor({
|
|
2447
|
+
amount: position.value,
|
|
2448
|
+
denomination: options.denomination,
|
|
2449
|
+
rates: options.rates,
|
|
2450
|
+
token: options.snapshot?.byAddress.get(assetToken.address),
|
|
2451
|
+
}),
|
|
2452
|
+
}
|
|
2453
|
+
: {}),
|
|
2454
|
+
vaultAddress,
|
|
2455
|
+
verified: options.verified,
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
declare namespace serializePosition {
|
|
2460
|
+
/** Values used to serialize one account position row. */
|
|
2461
|
+
type Options = {
|
|
2462
|
+
/** Currency the row valuation is denominated in (uppercase). */
|
|
2463
|
+
denomination?: string | undefined
|
|
2464
|
+
position: Actions.earn.getPosition.ReturnValue
|
|
2465
|
+
/** Consulted FX rate set, or undefined when unavailable or unnecessary. */
|
|
2466
|
+
rates: Awaited<ReturnType<typeof Valuation.ratesFor>>
|
|
2467
|
+
/** Curated token snapshot backing valuation, present when requested. */
|
|
2468
|
+
snapshot?: VerifiedTokens.Snapshot | undefined
|
|
2469
|
+
tokens: Tokens.resolveTokens.ReturnType
|
|
2470
|
+
vaultAddress: string
|
|
2471
|
+
verified: boolean
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
/** Renders a base-unit amount in the token's display currency and precision. */
|
|
2476
|
+
function valueFor(amount: bigint, token: z.output<typeof Tokens.schema.Token>) {
|
|
2477
|
+
return {
|
|
2478
|
+
amount: amount.toString(),
|
|
2479
|
+
currency: token.currency,
|
|
2480
|
+
decimals: token.decimals,
|
|
2481
|
+
formatted: core_Value.format(amount, token.decimals),
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
|
|
1702
2485
|
/** Gets one compatible Earn vault. */
|
|
1703
2486
|
export async function getVault(c: Context<App.Environment>, options: getVault.Options) {
|
|
1704
2487
|
const db = Db.get(c.get('dbCached'))
|