tapimo 0.13.0 → 0.14.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 +6064 -4829
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +1 -0
- package/dist/App.js.map +1 -1
- package/dist/Webhooks.js +2 -2
- package/dist/Webhooks.js.map +1 -1
- package/dist/admin/App.d.ts +1008 -917
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/apps/earn-vaults.d.ts +48 -0
- package/dist/admin/apps/earn-vaults.d.ts.map +1 -1
- package/dist/admin/apps/earn-vaults.js +2 -1
- package/dist/admin/apps/earn-vaults.js.map +1 -1
- package/dist/admin/apps/organizations.d.ts +92 -3
- package/dist/admin/apps/organizations.d.ts.map +1 -1
- package/dist/admin/apps/organizations.js +69 -7
- package/dist/admin/apps/organizations.js.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +74 -74
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.js +69 -69
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/apps/Zones.d.ts +12 -12
- package/dist/apps/Zones.js +3 -3
- package/dist/apps/data/App.d.ts +2909 -2523
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +11 -6
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/routes/addresses.d.ts +22 -22
- package/dist/apps/data/routes/earn.d.ts +183 -7
- package/dist/apps/data/routes/earn.d.ts.map +1 -1
- package/dist/apps/data/routes/earn.js +434 -75
- package/dist/apps/data/routes/earn.js.map +1 -1
- package/dist/apps/data/routes/exchanges.d.ts +15 -0
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
- package/dist/apps/data/routes/exchanges.js +180 -114
- package/dist/apps/data/routes/exchanges.js.map +1 -1
- package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
- package/dist/apps/data/routes/fee-amm.js +18 -19
- package/dist/apps/data/routes/fee-amm.js.map +1 -1
- package/dist/apps/data/routes/tokens.d.ts +93 -93
- package/dist/apps/data/routes/transfers.d.ts +12 -12
- package/dist/apps/data/routes/valuation.d.ts +30 -30
- package/dist/apps/data/routes/verified-tokens.d.ts +5 -5
- package/dist/apps/data/routes/webhooks.d.ts +805 -102
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +47 -5
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/funding/App.d.ts +456 -45
- package/dist/apps/funding/App.d.ts.map +1 -1
- package/dist/apps/funding/App.js +1 -1
- package/dist/apps/funding/App.js.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.d.ts +17 -17
- package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.js +1 -1
- package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
- package/dist/apps/funding/routes/transfers.d.ts +654 -73
- package/dist/apps/funding/routes/transfers.d.ts.map +1 -1
- package/dist/apps/funding/routes/transfers.js +211 -5
- package/dist/apps/funding/routes/transfers.js.map +1 -1
- package/dist/apps/management/App.d.ts +831 -387
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/Billing.d.ts +1 -1
- package/dist/apps/management/Billing.d.ts.map +1 -1
- package/dist/apps/management/Billing.js +17 -1
- package/dist/apps/management/Billing.js.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +76 -76
- package/dist/apps/management/routes/billing.d.ts +76 -76
- package/dist/apps/management/routes/faucet.d.ts +12 -12
- package/dist/apps/management/routes/invitations.d.ts +24 -24
- package/dist/apps/management/routes/invite-links.d.ts +46 -46
- package/dist/apps/management/routes/members.d.ts +46 -46
- package/dist/apps/management/routes/orgs.d.ts +20 -20
- package/dist/apps/management/routes/projects.d.ts +44 -44
- package/dist/apps/management/routes/usage.d.ts +32 -32
- package/dist/apps/management/routes/verified-token-requests.d.ts +16 -16
- package/dist/apps/management/routes/webhooks.d.ts +707 -57
- package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/management/routes/webhooks.js +21 -21
- package/dist/apps/management/routes/webhooks.js.map +1 -1
- package/dist/apps/relay/App.d.ts.map +1 -1
- package/dist/apps/relay/App.js +115 -23
- package/dist/apps/relay/App.js.map +1 -1
- package/dist/apps/relay/Sponsorships.d.ts.map +1 -1
- package/dist/apps/relay/Sponsorships.js +27 -1
- package/dist/apps/relay/Sponsorships.js.map +1 -1
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +22 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +8 -3
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +16 -1
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/fundingTransferTransactions.d.ts +38 -0
- package/dist/db/tables/fundingTransferTransactions.d.ts.map +1 -1
- package/dist/db/tables/fundingTransferTransactions.js +44 -3
- package/dist/db/tables/fundingTransferTransactions.js.map +1 -1
- package/dist/db/tables/fundingTransfers.d.ts +17 -0
- package/dist/db/tables/fundingTransfers.d.ts.map +1 -1
- package/dist/db/tables/fundingTransfers.js +21 -0
- package/dist/db/tables/fundingTransfers.js.map +1 -1
- package/dist/db/tables/organizations.d.ts +24 -0
- package/dist/db/tables/organizations.d.ts.map +1 -1
- package/dist/db/tables/organizations.js +27 -0
- package/dist/db/tables/organizations.js.map +1 -1
- package/dist/db/tables/projects.d.ts +25 -1
- package/dist/db/tables/projects.d.ts.map +1 -1
- package/dist/db/tables/projects.js +26 -0
- package/dist/db/tables/projects.js.map +1 -1
- package/dist/db/tables/sponsoredTransactions.d.ts +44 -0
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
- package/dist/db/tables/sponsoredTransactions.js +146 -13
- package/dist/db/tables/sponsoredTransactions.js.map +1 -1
- package/dist/db/tables/webhookSubscriptions.d.ts +10 -6
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
- package/dist/db/tables/webhookSubscriptions.js +9 -6
- package/dist/db/tables/webhookSubscriptions.js.map +1 -1
- package/dist/internal/EarnVaults.js +5 -5
- package/dist/internal/EarnVaults.js.map +1 -1
- package/dist/internal/Log.d.ts +1 -1
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/Viem.d.ts +16 -1
- package/dist/internal/Viem.d.ts.map +1 -1
- package/dist/internal/Viem.js +12 -2
- package/dist/internal/Viem.js.map +1 -1
- package/dist/internal/funding/DepositWebhook.d.ts.map +1 -1
- package/dist/internal/funding/DepositWebhook.js +2 -1
- package/dist/internal/funding/DepositWebhook.js.map +1 -1
- package/dist/internal/funding/Evidence.d.ts.map +1 -1
- package/dist/internal/funding/Evidence.js +2 -10
- package/dist/internal/funding/Evidence.js.map +1 -1
- package/dist/internal/funding/Provider.d.ts +40 -0
- package/dist/internal/funding/Provider.d.ts.map +1 -1
- package/dist/internal/funding/Provider.js +5 -0
- package/dist/internal/funding/Provider.js.map +1 -1
- package/dist/internal/funding/SourceObservation.d.ts.map +1 -1
- package/dist/internal/funding/SourceObservation.js +5 -7
- package/dist/internal/funding/SourceObservation.js.map +1 -1
- package/dist/internal/funding/Transfer.d.ts +101 -6
- package/dist/internal/funding/Transfer.d.ts.map +1 -1
- package/dist/internal/funding/Transfer.js +162 -34
- package/dist/internal/funding/Transfer.js.map +1 -1
- package/dist/internal/funding/TransferReconciliation.d.ts +52 -0
- package/dist/internal/funding/TransferReconciliation.d.ts.map +1 -0
- package/dist/internal/funding/TransferReconciliation.js +192 -0
- package/dist/internal/funding/TransferReconciliation.js.map +1 -0
- package/dist/internal/funding/TransferWebhook.d.ts +21 -0
- package/dist/internal/funding/TransferWebhook.d.ts.map +1 -0
- package/dist/internal/funding/TransferWebhook.js +43 -0
- package/dist/internal/funding/TransferWebhook.js.map +1 -0
- package/dist/internal/funding/index.d.ts +1 -0
- package/dist/internal/funding/index.d.ts.map +1 -1
- package/dist/internal/funding/index.js +1 -0
- package/dist/internal/funding/index.js.map +1 -1
- package/dist/internal/funding/providers/relay.d.ts.map +1 -1
- package/dist/internal/funding/providers/relay.js +3 -12
- package/dist/internal/funding/providers/relay.js.map +1 -1
- package/dist/internal/funding/providers/stargate.d.ts +99 -0
- package/dist/internal/funding/providers/stargate.d.ts.map +1 -1
- package/dist/internal/funding/providers/stargate.js +337 -35
- package/dist/internal/funding/providers/stargate.js.map +1 -1
- package/dist/internal/webhookDestinations/slack.d.ts.map +1 -1
- package/dist/internal/webhookDestinations/slack.js +16 -0
- package/dist/internal/webhookDestinations/slack.js.map +1 -1
- package/package.json +1 -1
- package/src/App.ts +1 -0
- package/src/Client.test-d.ts +83 -2
- package/src/Webhooks.ts +2 -2
- package/src/admin/apps/earn-vaults.ts +2 -1
- package/src/admin/apps/organizations.test.ts +53 -1
- package/src/admin/apps/organizations.ts +90 -5
- package/src/admin/ui/src/lib/organizations.test.ts +112 -1
- package/src/admin/ui/src/lib/organizations.ts +55 -0
- package/src/admin/ui/src/routes/organizations_.$orgId.tsx +116 -0
- package/src/admin/ui.generated.ts +69 -69
- package/src/apps/Zones.test.ts +3 -3
- package/src/apps/Zones.ts +3 -3
- package/src/apps/composition.test.ts +1 -0
- package/src/apps/data/App.ts +15 -6
- package/src/apps/data/routes/earn.test.ts +369 -14
- package/src/apps/data/routes/earn.ts +548 -79
- package/src/apps/data/routes/exchanges.test.ts +740 -15
- package/src/apps/data/routes/exchanges.ts +954 -753
- package/src/apps/data/routes/fee-amm.ts +17 -18
- package/src/apps/data/routes/webhooks.test.ts +21 -5
- package/src/apps/data/routes/webhooks.ts +63 -8
- package/src/apps/funding/App.ts +12 -2
- package/src/apps/funding/routes/deposit-addresses.test.ts +4 -4
- package/src/apps/funding/routes/deposit-addresses.ts +2 -2
- package/src/apps/funding/routes/transfers.test.ts +510 -19
- package/src/apps/funding/routes/transfers.ts +271 -5
- package/src/apps/management/Billing.ts +18 -5
- package/src/apps/management/routes/webhooks.test.ts +3 -3
- package/src/apps/management/routes/webhooks.ts +24 -25
- package/src/apps/relay/App.test.ts +212 -1
- package/src/apps/relay/App.ts +158 -29
- package/src/apps/relay/Sponsorships.ts +24 -1
- package/src/db/Db.ts +35 -0
- package/src/db/Schema.ts +17 -2
- package/src/db/tables/fundingTransferTransactions.test.ts +43 -0
- package/src/db/tables/fundingTransferTransactions.ts +79 -3
- package/src/db/tables/fundingTransfers.test.ts +26 -0
- package/src/db/tables/fundingTransfers.ts +36 -0
- package/src/db/tables/organizations.test.ts +41 -0
- package/src/db/tables/organizations.ts +49 -0
- package/src/db/tables/projects.test.ts +55 -0
- package/src/db/tables/projects.ts +46 -1
- package/src/db/tables/sponsoredTransactions.test.ts +338 -0
- package/src/db/tables/sponsoredTransactions.ts +197 -13
- package/src/db/tables/webhookSubscriptions.ts +18 -11
- package/src/internal/EarnVaults.ts +5 -5
- package/src/internal/Log.test-d.ts +2 -0
- package/src/internal/Log.ts +2 -0
- package/src/internal/Viem.ts +39 -3
- package/src/internal/Webhooks.test.ts +45 -0
- package/src/internal/funding/DepositWebhook.ts +2 -1
- package/src/internal/funding/Evidence.ts +2 -13
- package/src/internal/funding/Provider.ts +43 -0
- package/src/internal/funding/SourceObservation.ts +5 -9
- package/src/internal/funding/Transfer.test.ts +155 -0
- package/src/internal/funding/Transfer.ts +254 -37
- package/src/internal/funding/TransferReconciliation.test.ts +147 -0
- package/src/internal/funding/TransferReconciliation.ts +264 -0
- package/src/internal/funding/TransferWebhook.test.ts +128 -0
- package/src/internal/funding/TransferWebhook.ts +67 -0
- package/src/internal/funding/index.test.ts +1 -0
- package/src/internal/funding/index.ts +1 -0
- package/src/internal/funding/providers/relay.ts +3 -15
- package/src/internal/funding/providers/stargate.test.ts +169 -4
- package/src/internal/funding/providers/stargate.ts +498 -51
- package/src/internal/webhookDestinations/slack.ts +15 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Context, Hono } from 'hono'
|
|
2
2
|
import { Value as core_Value } from 'ox'
|
|
3
|
-
import type
|
|
3
|
+
import { Abis, type Actions } from 'viem/tempo'
|
|
4
4
|
import {
|
|
5
5
|
type Chain,
|
|
6
6
|
ContractFunctionExecutionError,
|
|
@@ -130,7 +130,7 @@ export namespace schema {
|
|
|
130
130
|
z.strictObject({
|
|
131
131
|
address: Schema.Address.check(
|
|
132
132
|
z.describe('Current vault engine contract address.'),
|
|
133
|
-
z.meta({ examples: ['
|
|
133
|
+
z.meta({ examples: ['0x59d27987bc7cc1521bba5f688a170c65840cbf43'] }),
|
|
134
134
|
),
|
|
135
135
|
type: z
|
|
136
136
|
.nullable(engineType)
|
|
@@ -139,7 +139,7 @@ export namespace schema {
|
|
|
139
139
|
.nullable(
|
|
140
140
|
Schema.Address.check(
|
|
141
141
|
z.describe('Yield venue contract bound to the engine.'),
|
|
142
|
-
z.meta({ examples: ['
|
|
142
|
+
z.meta({ examples: ['0x9a044ae05e5e6290dcf56afd69548565e957a626'] }),
|
|
143
143
|
),
|
|
144
144
|
)
|
|
145
145
|
.check(z.describe('Yield venue, or null when the engine does not expose one.')),
|
|
@@ -171,22 +171,22 @@ export namespace schema {
|
|
|
171
171
|
.strictObject({
|
|
172
172
|
chainId: Schema.ChainId.check(
|
|
173
173
|
z.describe('Private Zone chain id.'),
|
|
174
|
-
z.meta({ examples: [
|
|
174
|
+
z.meta({ examples: [421700001] }),
|
|
175
175
|
),
|
|
176
176
|
inputTokens: z
|
|
177
177
|
.array(Schema.Address)
|
|
178
178
|
.check(
|
|
179
179
|
z.describe('Tokens accepted by private deposits.'),
|
|
180
|
-
z.meta({ examples: [['
|
|
180
|
+
z.meta({ examples: [['0x20c0000000000000000000006fd9a167923ba194']] }),
|
|
181
181
|
),
|
|
182
182
|
name: z
|
|
183
183
|
.string()
|
|
184
|
-
.check(z.describe('Private Zone name.'), z.meta({ examples: ['zone-
|
|
184
|
+
.check(z.describe('Private Zone name.'), z.meta({ examples: ['zone-mainnet-internal'] })),
|
|
185
185
|
outputTokens: z
|
|
186
186
|
.array(Schema.Address)
|
|
187
187
|
.check(
|
|
188
188
|
z.describe('Tokens supported by private redemptions.'),
|
|
189
|
-
z.meta({ examples: [['
|
|
189
|
+
z.meta({ examples: [['0x20c0000000000000000000006fd9a167923ba194']] }),
|
|
190
190
|
),
|
|
191
191
|
})
|
|
192
192
|
.check(z.describe('Private Zone route configured for a verified vault.'))
|
|
@@ -195,7 +195,7 @@ export namespace schema {
|
|
|
195
195
|
.strictObject({
|
|
196
196
|
chainId: Schema.ChainId.check(
|
|
197
197
|
z.describe('Private Zone chain id.'),
|
|
198
|
-
z.meta({ examples: [
|
|
198
|
+
z.meta({ examples: [421700001] }),
|
|
199
199
|
),
|
|
200
200
|
deploymentBlock: z
|
|
201
201
|
.number()
|
|
@@ -203,26 +203,26 @@ export namespace schema {
|
|
|
203
203
|
z.int(),
|
|
204
204
|
z.nonnegative(),
|
|
205
205
|
z.describe('Parent-chain block that deployed the Earn router.'),
|
|
206
|
-
z.meta({ examples: [
|
|
206
|
+
z.meta({ examples: [35816964] }),
|
|
207
207
|
),
|
|
208
208
|
earnRouter: Schema.Address.check(
|
|
209
209
|
z.describe('Parent-chain router for this Zone and Earn vault.'),
|
|
210
|
-
z.meta({ examples: ['
|
|
210
|
+
z.meta({ examples: ['0x8117e0ba6239b9695f780deb010f72a2fa4bdfb6'] }),
|
|
211
211
|
),
|
|
212
212
|
inputTokens: z
|
|
213
213
|
.array(Schema.Address)
|
|
214
214
|
.check(
|
|
215
215
|
z.describe('Tokens accepted by private deposits.'),
|
|
216
|
-
z.meta({ examples: [['
|
|
216
|
+
z.meta({ examples: [['0x20c0000000000000000000006fd9a167923ba194']] }),
|
|
217
217
|
),
|
|
218
218
|
name: z
|
|
219
219
|
.string()
|
|
220
|
-
.check(z.describe('Private Zone name.'), z.meta({ examples: ['zone-
|
|
220
|
+
.check(z.describe('Private Zone name.'), z.meta({ examples: ['zone-mainnet-internal'] })),
|
|
221
221
|
outputTokens: z
|
|
222
222
|
.array(Schema.Address)
|
|
223
223
|
.check(
|
|
224
224
|
z.describe('Tokens returned by private redemptions.'),
|
|
225
|
-
z.meta({ examples: [['
|
|
225
|
+
z.meta({ examples: [['0x20c0000000000000000000006fd9a167923ba194']] }),
|
|
226
226
|
),
|
|
227
227
|
})
|
|
228
228
|
.check(z.describe('Verified Earn route through one private Zone.'))
|
|
@@ -263,7 +263,22 @@ export namespace schema {
|
|
|
263
263
|
'Annualized share-price growth over the selected window, when requested via `include=apy` or by selecting `apy.window`. Null when the rate could not be measured: the vault predates the window, a boundary or historical quote is unavailable, the engine changed inside the window, or the growth is too extreme to annualize as a ratio.',
|
|
264
264
|
),
|
|
265
265
|
),
|
|
266
|
-
assetToken: Tokens.schema.Token.check(
|
|
266
|
+
assetToken: Tokens.schema.Token.check(
|
|
267
|
+
z.describe('Asset accepted by the vault.'),
|
|
268
|
+
z.meta({
|
|
269
|
+
examples: [
|
|
270
|
+
{
|
|
271
|
+
address: '0x20c0000000000000000000000000000000000000',
|
|
272
|
+
currency: 'USD',
|
|
273
|
+
decimals: 6,
|
|
274
|
+
id: '0x20c0000000000000000000000000000000000000',
|
|
275
|
+
name: 'PathUSD',
|
|
276
|
+
symbol: 'pathUSD',
|
|
277
|
+
verified: true,
|
|
278
|
+
},
|
|
279
|
+
],
|
|
280
|
+
}),
|
|
281
|
+
),
|
|
267
282
|
capabilities: z
|
|
268
283
|
.optional(EarnVaults.schema.Discovery.shape.capabilities)
|
|
269
284
|
.check(z.describe('Vault capabilities, when requested.')),
|
|
@@ -271,21 +286,28 @@ export namespace schema {
|
|
|
271
286
|
.nullable(z.string())
|
|
272
287
|
.check(
|
|
273
288
|
z.describe('Curated vault description, or null when the vault is not verified.'),
|
|
274
|
-
z.meta({ examples: ['
|
|
289
|
+
z.meta({ examples: ['deelUSD deposited into the canonical Tempo Earn deployment.'] }),
|
|
275
290
|
),
|
|
276
291
|
engine,
|
|
277
292
|
id: Schema.Address.check(
|
|
278
293
|
z.describe('Stable vault id, equal to the lowercase vault address.'),
|
|
279
|
-
z.meta({ examples: ['
|
|
294
|
+
z.meta({ examples: ['0x4f94590b636f5878bce585e82379de81e1ec174f'] }),
|
|
280
295
|
),
|
|
281
296
|
instantLiquidity: EarnVaults.schema.Discovery.shape.instantLiquidity.check(
|
|
282
297
|
z.describe(
|
|
283
298
|
'Assets the venue would release right now, capped at the vault backing, in asset base units. Null when the engine interface cannot report a reliable number.',
|
|
284
299
|
),
|
|
285
300
|
),
|
|
301
|
+
instantLiquidityValue: z
|
|
302
|
+
.optional(z.nullable(value))
|
|
303
|
+
.check(
|
|
304
|
+
z.describe(
|
|
305
|
+
'Instant liquidity valued in USD, when requested via `include=tvl`, or null when liquidity is unavailable or the asset has no priced display currency.',
|
|
306
|
+
),
|
|
307
|
+
),
|
|
286
308
|
label: z
|
|
287
309
|
.string()
|
|
288
|
-
.check(z.describe('Vault display label.'), z.meta({ examples: ['
|
|
310
|
+
.check(z.describe('Vault display label.'), z.meta({ examples: ['deelUSD Earn'] })),
|
|
289
311
|
sharePrice: z
|
|
290
312
|
.nullable(value)
|
|
291
313
|
.check(
|
|
@@ -293,12 +315,27 @@ export namespace schema {
|
|
|
293
315
|
'Assets returned for one whole earn share, in the asset display currency, or null when the engine cannot quote an exit.',
|
|
294
316
|
),
|
|
295
317
|
),
|
|
296
|
-
shareToken: Tokens.schema.Token.check(
|
|
318
|
+
shareToken: Tokens.schema.Token.check(
|
|
319
|
+
z.describe('Token representing vault shares.'),
|
|
320
|
+
z.meta({
|
|
321
|
+
examples: [
|
|
322
|
+
{
|
|
323
|
+
address: '0x20c000000000000000000000c3268d803b51d448',
|
|
324
|
+
currency: 'USD',
|
|
325
|
+
decimals: 6,
|
|
326
|
+
id: '0x20c000000000000000000000c3268d803b51d448',
|
|
327
|
+
name: 'Sentora pathUSD (Earn)',
|
|
328
|
+
symbol: 'senpathUSDE',
|
|
329
|
+
verified: false,
|
|
330
|
+
},
|
|
331
|
+
],
|
|
332
|
+
}),
|
|
333
|
+
),
|
|
297
334
|
slug: z
|
|
298
335
|
.nullable(z.string())
|
|
299
336
|
.check(
|
|
300
337
|
z.describe('Curated URL slug, or null when the vault is not verified.'),
|
|
301
|
-
z.meta({ examples: ['
|
|
338
|
+
z.meta({ examples: ['deelusd-earn'] }),
|
|
302
339
|
),
|
|
303
340
|
state: EarnVaults.schema.Discovery.shape.state,
|
|
304
341
|
tvl: z
|
|
@@ -310,7 +347,7 @@ export namespace schema {
|
|
|
310
347
|
),
|
|
311
348
|
vaultAddress: Schema.Address.check(
|
|
312
349
|
z.describe('Earn vault contract address.'),
|
|
313
|
-
z.meta({ examples: ['
|
|
350
|
+
z.meta({ examples: ['0x4f94590b636f5878bce585e82379de81e1ec174f'] }),
|
|
314
351
|
),
|
|
315
352
|
verified: z
|
|
316
353
|
.boolean()
|
|
@@ -327,12 +364,12 @@ export namespace schema {
|
|
|
327
364
|
examples: [
|
|
328
365
|
[
|
|
329
366
|
{
|
|
330
|
-
chainId:
|
|
331
|
-
deploymentBlock:
|
|
332
|
-
earnRouter: '
|
|
333
|
-
inputTokens: ['
|
|
334
|
-
name: 'zone-
|
|
335
|
-
outputTokens: ['
|
|
367
|
+
chainId: 421700001,
|
|
368
|
+
deploymentBlock: 35816964,
|
|
369
|
+
earnRouter: '0x8117e0ba6239b9695f780deb010f72a2fa4bdfb6',
|
|
370
|
+
inputTokens: ['0x20c0000000000000000000006fd9a167923ba194'],
|
|
371
|
+
name: 'zone-mainnet-internal',
|
|
372
|
+
outputTokens: ['0x20c0000000000000000000006fd9a167923ba194'],
|
|
336
373
|
},
|
|
337
374
|
],
|
|
338
375
|
],
|
|
@@ -373,7 +410,7 @@ export namespace schema {
|
|
|
373
410
|
z.meta({ examples: ['0'] }),
|
|
374
411
|
),
|
|
375
412
|
assetBalance: Schema.DecimalString.check(
|
|
376
|
-
z.describe('Assets this account
|
|
413
|
+
z.describe('Assets held by this account at the observation block, in asset base units.'),
|
|
377
414
|
z.meta({ examples: ['250000000'] }),
|
|
378
415
|
),
|
|
379
416
|
assetToken: Schema.tokenAddress('0x20c000000000000000000000b9537d11c60e8b50').check(
|
|
@@ -388,7 +425,7 @@ export namespace schema {
|
|
|
388
425
|
z.meta({ examples: ['0'] }),
|
|
389
426
|
),
|
|
390
427
|
shareBalance: Schema.DecimalString.check(
|
|
391
|
-
z.describe('Earn shares this account
|
|
428
|
+
z.describe('Earn shares held by this account at the observation block, in share base units.'),
|
|
392
429
|
z.meta({ examples: ['99500000'] }),
|
|
393
430
|
),
|
|
394
431
|
shareToken: Schema.tokenAddress('0x20c000000000000000000000300e14ab91a10769').check(
|
|
@@ -396,12 +433,37 @@ export namespace schema {
|
|
|
396
433
|
),
|
|
397
434
|
value: Schema.DecimalString.check(
|
|
398
435
|
z.describe(
|
|
399
|
-
'
|
|
436
|
+
'Asset value of this account’s earn shares at the observation block, including fees, in asset base units.',
|
|
400
437
|
),
|
|
401
438
|
z.meta({ examples: ['100000000'] }),
|
|
402
439
|
),
|
|
403
440
|
})
|
|
404
441
|
|
|
442
|
+
const lifetimeCashFlows = z.union([
|
|
443
|
+
z.strictObject({
|
|
444
|
+
status: z
|
|
445
|
+
.literal('complete')
|
|
446
|
+
.check(
|
|
447
|
+
z.describe('Cash flows are complete through the indexed boundary.'),
|
|
448
|
+
z.meta({ examples: ['complete'] }),
|
|
449
|
+
),
|
|
450
|
+
totalDeposited: Schema.DecimalString.check(
|
|
451
|
+
z.describe('Assets deposited with this account as receiver over indexed vault history.'),
|
|
452
|
+
),
|
|
453
|
+
totalWithdrawn: Schema.DecimalString.check(
|
|
454
|
+
z.describe('Assets returned by completed withdrawals over indexed vault history.'),
|
|
455
|
+
),
|
|
456
|
+
}),
|
|
457
|
+
z.strictObject({
|
|
458
|
+
status: z
|
|
459
|
+
.literal('incomplete_history')
|
|
460
|
+
.check(
|
|
461
|
+
z.describe('Cash flows are omitted because asset deposit history is incomplete.'),
|
|
462
|
+
z.meta({ examples: ['incomplete_history'] }),
|
|
463
|
+
),
|
|
464
|
+
}),
|
|
465
|
+
])
|
|
466
|
+
|
|
405
467
|
const addressPosition = z.strictObject({
|
|
406
468
|
assetAmount: value.check(
|
|
407
469
|
z.describe('Current asset value of the held shares, including fees, in `assetToken` units.'),
|
|
@@ -409,8 +471,11 @@ export namespace schema {
|
|
|
409
471
|
assetToken: Tokens.schema.Token.check(z.describe('Asset accepted by the vault.')),
|
|
410
472
|
id: Schema.Address.check(
|
|
411
473
|
z.describe('Stable resource ID for this position, equal to the vault address.'),
|
|
412
|
-
z.meta({ examples: ['
|
|
474
|
+
z.meta({ examples: ['0x4f94590b636f5878bce585e82379de81e1ec174f'] }),
|
|
413
475
|
),
|
|
476
|
+
lifetimeCashFlows: z
|
|
477
|
+
.optional(lifetimeCashFlows)
|
|
478
|
+
.check(z.describe('Lifetime deposits and completed withdrawals, when requested.')),
|
|
414
479
|
shareAmount: value.check(
|
|
415
480
|
z.describe('Earn share balance this account holds, in `shareToken` units.'),
|
|
416
481
|
),
|
|
@@ -425,7 +490,7 @@ export namespace schema {
|
|
|
425
490
|
),
|
|
426
491
|
vaultAddress: Schema.Address.check(
|
|
427
492
|
z.describe('Earn vault contract address holding this position.'),
|
|
428
|
-
z.meta({ examples: ['
|
|
493
|
+
z.meta({ examples: ['0x4f94590b636f5878bce585e82379de81e1ec174f'] }),
|
|
429
494
|
),
|
|
430
495
|
verified: z
|
|
431
496
|
.boolean()
|
|
@@ -476,6 +541,18 @@ export namespace schema {
|
|
|
476
541
|
),
|
|
477
542
|
z.meta({ examples: ['0'] }),
|
|
478
543
|
),
|
|
544
|
+
totalDeposited: Schema.DecimalString.check(
|
|
545
|
+
z.describe(
|
|
546
|
+
'Assets deposited with this account as receiver over complete indexed vault history.',
|
|
547
|
+
),
|
|
548
|
+
z.meta({ examples: ['1000000'] }),
|
|
549
|
+
),
|
|
550
|
+
totalWithdrawn: Schema.DecimalString.check(
|
|
551
|
+
z.describe(
|
|
552
|
+
'Assets returned by completed redemptions and exact withdrawals initiated by this account over complete indexed vault history.',
|
|
553
|
+
),
|
|
554
|
+
z.meta({ examples: ['1000000'] }),
|
|
555
|
+
),
|
|
479
556
|
period: z
|
|
480
557
|
.literal('lifetime')
|
|
481
558
|
.check(
|
|
@@ -548,9 +625,23 @@ export namespace schema {
|
|
|
548
625
|
}),
|
|
549
626
|
z.extend(earningsBase, {
|
|
550
627
|
period: z
|
|
551
|
-
.
|
|
628
|
+
.literal('30d')
|
|
629
|
+
.check(
|
|
630
|
+
z.describe('Trailing period with an unresolved async redemption.'),
|
|
631
|
+
z.meta({ examples: ['30d'] }),
|
|
632
|
+
),
|
|
633
|
+
status: z
|
|
634
|
+
.literal('pending_redemption')
|
|
635
|
+
.check(
|
|
636
|
+
z.describe('Earnings are unavailable while an async redemption remains open.'),
|
|
637
|
+
z.meta({ examples: ['pending_redemption'] }),
|
|
638
|
+
),
|
|
639
|
+
}),
|
|
640
|
+
z.extend(earningsBase, {
|
|
641
|
+
period: z
|
|
642
|
+
.literal('lifetime')
|
|
552
643
|
.check(
|
|
553
|
-
z.describe('
|
|
644
|
+
z.describe('Lifetime period with an unresolved async redemption.'),
|
|
554
645
|
z.meta({ examples: ['lifetime'] }),
|
|
555
646
|
),
|
|
556
647
|
status: z
|
|
@@ -559,6 +650,12 @@ export namespace schema {
|
|
|
559
650
|
z.describe('Earnings are unavailable while an async redemption remains open.'),
|
|
560
651
|
z.meta({ examples: ['pending_redemption'] }),
|
|
561
652
|
),
|
|
653
|
+
totalDeposited: Schema.DecimalString.check(
|
|
654
|
+
z.describe('Assets deposited over complete indexed vault history.'),
|
|
655
|
+
),
|
|
656
|
+
totalWithdrawn: Schema.DecimalString.check(
|
|
657
|
+
z.describe('Assets withdrawn over complete indexed vault history.'),
|
|
658
|
+
),
|
|
562
659
|
}),
|
|
563
660
|
])
|
|
564
661
|
|
|
@@ -654,6 +751,10 @@ export namespace schema {
|
|
|
654
751
|
.strictObject({
|
|
655
752
|
chainId: Schema.ChainIdQuery,
|
|
656
753
|
cursor: Schema.Cursor,
|
|
754
|
+
include: Schema.includeQuery(
|
|
755
|
+
z.enum(['earnings']),
|
|
756
|
+
'Comma-separated position fields to include, such as `earnings`.',
|
|
757
|
+
),
|
|
657
758
|
limit: Schema.Limit,
|
|
658
759
|
'valuation.currency': Schema.Denomination.check(
|
|
659
760
|
z.describe(
|
|
@@ -727,11 +828,13 @@ export namespace schema {
|
|
|
727
828
|
|
|
728
829
|
/** Schemas for the getEarnVaultPosition operation. */
|
|
729
830
|
export namespace getEarnVaultPosition {
|
|
831
|
+
const asOf = z.iso.datetime({ offset: true })
|
|
832
|
+
|
|
730
833
|
/** Path parameters identifying the Earn vault and account. */
|
|
731
834
|
export const Params = z
|
|
732
835
|
.strictObject({
|
|
733
836
|
address: Schema.Address.check(z.describe('Account whose earn position to read.')),
|
|
734
|
-
vaultId: Schema.address('
|
|
837
|
+
vaultId: Schema.address('0x4f94590b636f5878bce585e82379de81e1ec174f').check(
|
|
735
838
|
z.describe('Earn vault contract address.'),
|
|
736
839
|
),
|
|
737
840
|
})
|
|
@@ -739,13 +842,56 @@ export namespace schema {
|
|
|
739
842
|
|
|
740
843
|
/** Query parameters selecting the chain containing the vault. */
|
|
741
844
|
export const Query = z
|
|
742
|
-
.strictObject({
|
|
845
|
+
.strictObject({
|
|
846
|
+
asOf: z
|
|
847
|
+
.optional(asOf)
|
|
848
|
+
.check(
|
|
849
|
+
z.describe(
|
|
850
|
+
'ISO 8601 timestamp at which to read the position. Resolves the latest indexed block at or before this time.',
|
|
851
|
+
),
|
|
852
|
+
z.meta({ examples: ['2026-07-21T09:00:00.000Z'] }),
|
|
853
|
+
),
|
|
854
|
+
chainId: Schema.ChainIdQuery,
|
|
855
|
+
})
|
|
743
856
|
.check(z.describe('Query parameters selecting an earn vault position.'))
|
|
744
857
|
|
|
745
|
-
|
|
858
|
+
const Block = z
|
|
859
|
+
.strictObject({
|
|
860
|
+
number: z
|
|
861
|
+
.number()
|
|
862
|
+
.check(
|
|
863
|
+
z.int(),
|
|
864
|
+
z.nonnegative(),
|
|
865
|
+
z.describe('Indexed observation block.'),
|
|
866
|
+
z.meta({ examples: [31039238] }),
|
|
867
|
+
),
|
|
868
|
+
timestamp: z.iso
|
|
869
|
+
.datetime()
|
|
870
|
+
.check(
|
|
871
|
+
z.describe('Timestamp of the indexed observation block.'),
|
|
872
|
+
z.meta({ examples: ['2026-07-21T09:00:00.000Z'] }),
|
|
873
|
+
),
|
|
874
|
+
})
|
|
875
|
+
.check(z.describe('Indexed block used for a historical Earn position observation.'))
|
|
876
|
+
|
|
877
|
+
/** Current or historical asset and Earn-share state for one account in one vault. */
|
|
746
878
|
export const Response = Schema.describe(
|
|
747
|
-
position,
|
|
748
|
-
|
|
879
|
+
z.extend(position, {
|
|
880
|
+
asOf: z
|
|
881
|
+
.optional(asOf)
|
|
882
|
+
.check(
|
|
883
|
+
z.describe('Requested historical observation time, when supplied.'),
|
|
884
|
+
z.meta({ examples: ['2026-07-21T09:00:00.000Z'] }),
|
|
885
|
+
),
|
|
886
|
+
block: z
|
|
887
|
+
.optional(Block)
|
|
888
|
+
.check(
|
|
889
|
+
z.describe(
|
|
890
|
+
'Indexed block used for a historical Earn position observation, when requested.',
|
|
891
|
+
),
|
|
892
|
+
),
|
|
893
|
+
}),
|
|
894
|
+
'Current or historical asset and earn-share balances, allowances, and asset value for an account in one earn vault.',
|
|
749
895
|
)
|
|
750
896
|
}
|
|
751
897
|
|
|
@@ -807,7 +953,7 @@ export function earn(options: earn.Options = {}) {
|
|
|
807
953
|
Cache.response<App.Environment>({
|
|
808
954
|
cacheControl: Cache.policies.state,
|
|
809
955
|
key: (c) => vaultCacheKey(c, schema.getEarnVaults.Query),
|
|
810
|
-
name: 'tempo-api:earn-vaults:
|
|
956
|
+
name: 'tempo-api:earn-vaults:v4',
|
|
811
957
|
}),
|
|
812
958
|
async (c) => {
|
|
813
959
|
if (Auth.narrowAccess) return Auth.paidAccessError(c)
|
|
@@ -851,7 +997,7 @@ export function earn(options: earn.Options = {}) {
|
|
|
851
997
|
Cache.response<App.Environment>({
|
|
852
998
|
cacheControl: Cache.policies.state,
|
|
853
999
|
key: (c) => vaultCacheKey(c, schema.getVerifiedEarnVaults.Query),
|
|
854
|
-
name: 'tempo-api:earn-vaults:
|
|
1000
|
+
name: 'tempo-api:earn-vaults:v4',
|
|
855
1001
|
}),
|
|
856
1002
|
async (c) => {
|
|
857
1003
|
if (Auth.narrowAccess) return Auth.paidAccessError(c)
|
|
@@ -884,7 +1030,7 @@ export function earn(options: earn.Options = {}) {
|
|
|
884
1030
|
}),
|
|
885
1031
|
OpenApi.describeRoute({
|
|
886
1032
|
description:
|
|
887
|
-
'Lists every earn vault where an account currently holds shares, with
|
|
1033
|
+
'Lists every earn vault where an account currently holds shares, with current balances and optional lifetime cash flows.',
|
|
888
1034
|
operationId: 'getEarnAddressPositions',
|
|
889
1035
|
responses: OpenApi.responses({
|
|
890
1036
|
errors: {
|
|
@@ -911,7 +1057,7 @@ export function earn(options: earn.Options = {}) {
|
|
|
911
1057
|
Cache.response<App.Environment>({
|
|
912
1058
|
cacheControl: Cache.policies.noStore,
|
|
913
1059
|
key: (c) => Cache.urlKey(c, schema.getEarnAddressPositions.Query),
|
|
914
|
-
name: 'tempo-api:earn-address-positions:
|
|
1060
|
+
name: 'tempo-api:earn-address-positions:v2',
|
|
915
1061
|
}),
|
|
916
1062
|
async (c) => {
|
|
917
1063
|
if (Auth.narrowAccess) return Auth.paidAccessError(c)
|
|
@@ -964,7 +1110,7 @@ export function earn(options: earn.Options = {}) {
|
|
|
964
1110
|
}),
|
|
965
1111
|
OpenApi.describeRoute({
|
|
966
1112
|
description:
|
|
967
|
-
'Gets an account’s asset and earn-share balances, allowances, and
|
|
1113
|
+
'Gets an account’s asset and earn-share balances, allowances, and asset value for one earn vault, optionally at an ISO 8601 timestamp.',
|
|
968
1114
|
operationId: 'getEarnVaultPosition',
|
|
969
1115
|
responses: OpenApi.responses({
|
|
970
1116
|
errors: {
|
|
@@ -983,17 +1129,18 @@ export function earn(options: earn.Options = {}) {
|
|
|
983
1129
|
502: 'Could not read the earn vault position.',
|
|
984
1130
|
},
|
|
985
1131
|
success: {
|
|
986
|
-
description: 'Current earn position for one account in one vault.',
|
|
1132
|
+
description: 'Current or historical earn position for one account in one vault.',
|
|
987
1133
|
schema: schema.getEarnVaultPosition.Response,
|
|
988
1134
|
},
|
|
989
1135
|
}),
|
|
990
1136
|
summary: 'Get account position',
|
|
991
1137
|
tags: ['Earn'],
|
|
992
1138
|
}),
|
|
1139
|
+
// Account holdings must read fresh immediately after a deposit or redemption.
|
|
993
1140
|
Cache.response<App.Environment>({
|
|
994
|
-
cacheControl: Cache.policies.
|
|
1141
|
+
cacheControl: Cache.policies.noStore,
|
|
995
1142
|
key: (c) => Cache.urlKey(c, schema.getEarnVaultPosition.Query),
|
|
996
|
-
name: 'tempo-api:earn-vault-positions:
|
|
1143
|
+
name: 'tempo-api:earn-vault-positions:v2',
|
|
997
1144
|
}),
|
|
998
1145
|
async (c) => {
|
|
999
1146
|
if (Auth.narrowAccess) return Auth.paidAccessError(c)
|
|
@@ -1023,35 +1170,85 @@ export function earn(options: earn.Options = {}) {
|
|
|
1023
1170
|
const { address, vaultId } = c.req.valid('param')
|
|
1024
1171
|
const query = c.req.valid('query')
|
|
1025
1172
|
const chainId = query.chainId ?? c.get('chainId')
|
|
1173
|
+
const client = c.get('getClient')(chainId)
|
|
1026
1174
|
try {
|
|
1027
|
-
const
|
|
1028
|
-
.
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
Response.validated(schema.getEarnVaultPosition.Response, {
|
|
1032
|
-
account: address,
|
|
1033
|
-
assetAllowance: position.assetAllowance.toString(),
|
|
1034
|
-
assetBalance: position.assetBalance.toString(),
|
|
1035
|
-
assetToken: position.assetToken,
|
|
1036
|
-
id: address,
|
|
1037
|
-
shareAllowance: position.shareAllowance.toString(),
|
|
1038
|
-
shareBalance: position.shareBalance.toString(),
|
|
1039
|
-
shareToken: position.shareToken,
|
|
1040
|
-
value: position.value.toString(),
|
|
1041
|
-
}),
|
|
1042
|
-
200,
|
|
1043
|
-
)
|
|
1044
|
-
} catch (cause) {
|
|
1175
|
+
const block =
|
|
1176
|
+
query.asOf === undefined
|
|
1177
|
+
? undefined
|
|
1178
|
+
: await getIndexedBlockAtOrBefore(c.get('getTidx')(chainId), query.asOf)
|
|
1045
1179
|
try {
|
|
1046
|
-
await
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1180
|
+
const position = await client.earn.getPosition({
|
|
1181
|
+
account: address,
|
|
1182
|
+
...(block ? { blockNumber: BigInt(block.number) } : {}),
|
|
1183
|
+
vault: vaultId,
|
|
1184
|
+
})
|
|
1185
|
+
return c.json(
|
|
1186
|
+
Response.validated(schema.getEarnVaultPosition.Response, {
|
|
1187
|
+
account: address,
|
|
1188
|
+
assetAllowance: position.assetAllowance.toString(),
|
|
1189
|
+
assetBalance: position.assetBalance.toString(),
|
|
1190
|
+
assetToken: position.assetToken,
|
|
1191
|
+
asOf: query.asOf,
|
|
1192
|
+
block,
|
|
1193
|
+
id: address,
|
|
1194
|
+
shareAllowance: position.shareAllowance.toString(),
|
|
1195
|
+
shareBalance: position.shareBalance.toString(),
|
|
1196
|
+
shareToken: position.shareToken,
|
|
1197
|
+
value: position.value.toString(),
|
|
1198
|
+
}),
|
|
1199
|
+
200,
|
|
1200
|
+
)
|
|
1201
|
+
} catch (cause) {
|
|
1202
|
+
if (block) {
|
|
1203
|
+
try {
|
|
1204
|
+
// These immutable bindings establish historical vault identity
|
|
1205
|
+
// without repeating fallible token or redemption subreads.
|
|
1206
|
+
const [asset, shareToken] = await Promise.all([
|
|
1207
|
+
client.readContract({
|
|
1208
|
+
abi: Abis.earnVault,
|
|
1209
|
+
address: vaultId,
|
|
1210
|
+
blockNumber: BigInt(block.number),
|
|
1211
|
+
functionName: 'asset',
|
|
1212
|
+
}),
|
|
1213
|
+
client.readContract({
|
|
1214
|
+
abi: Abis.earnVault,
|
|
1215
|
+
address: vaultId,
|
|
1216
|
+
blockNumber: BigInt(block.number),
|
|
1217
|
+
functionName: 'earnShare',
|
|
1218
|
+
}),
|
|
1219
|
+
])
|
|
1220
|
+
if (asset === zeroAddress || shareToken === zeroAddress)
|
|
1221
|
+
return Response.error(c, {
|
|
1222
|
+
code: 'earn_vault_not_found',
|
|
1223
|
+
message: 'Earn vault not found',
|
|
1224
|
+
status: 404,
|
|
1225
|
+
})
|
|
1226
|
+
} catch (identityCause) {
|
|
1227
|
+
if (isIncompatible(identityCause))
|
|
1228
|
+
return Response.error(c, {
|
|
1229
|
+
code: 'earn_vault_not_found',
|
|
1230
|
+
message: 'Earn vault not found',
|
|
1231
|
+
status: 404,
|
|
1232
|
+
})
|
|
1233
|
+
return Response.upstream(c, identityCause)
|
|
1234
|
+
}
|
|
1235
|
+
return Response.upstream(c, cause)
|
|
1236
|
+
}
|
|
1237
|
+
try {
|
|
1238
|
+
await resolveVaultDiscovery(c, { chainId, include: [], vaultAddress: vaultId })
|
|
1239
|
+
} catch (vaultCause) {
|
|
1240
|
+
if (isIncompatible(vaultCause))
|
|
1241
|
+
return Response.error(c, {
|
|
1242
|
+
code: 'earn_vault_not_found',
|
|
1243
|
+
message: 'Earn vault not found',
|
|
1244
|
+
status: 404,
|
|
1245
|
+
})
|
|
1246
|
+
}
|
|
1247
|
+
return Response.upstream(c, cause)
|
|
1054
1248
|
}
|
|
1249
|
+
} catch (cause) {
|
|
1250
|
+
if (cause instanceof IndexedBlockNotFoundError || cause instanceof IndexedCoverageError)
|
|
1251
|
+
return Response.error(c, { code: 'query_invalid', message: cause.message, status: 400 })
|
|
1055
1252
|
return Response.upstream(c, cause)
|
|
1056
1253
|
}
|
|
1057
1254
|
},
|
|
@@ -1098,7 +1295,7 @@ export function earn(options: earn.Options = {}) {
|
|
|
1098
1295
|
Cache.response<App.Environment>({
|
|
1099
1296
|
cacheControl: Cache.policies.state,
|
|
1100
1297
|
key: (c) => Cache.urlKey(c, schema.getEarnVaultEarnings.Query),
|
|
1101
|
-
name: 'tempo-api:earn-vault-earnings:
|
|
1298
|
+
name: 'tempo-api:earn-vault-earnings:v4',
|
|
1102
1299
|
}),
|
|
1103
1300
|
async (c) => {
|
|
1104
1301
|
if (Auth.narrowAccess) return Auth.paidAccessError(c)
|
|
@@ -1185,7 +1382,7 @@ export function earn(options: earn.Options = {}) {
|
|
|
1185
1382
|
Cache.response<App.Environment>({
|
|
1186
1383
|
cacheControl: Cache.policies.state,
|
|
1187
1384
|
key: (c) => vaultCacheKey(c, schema.getEarnVault.Query),
|
|
1188
|
-
name: 'tempo-api:earn-vaults:
|
|
1385
|
+
name: 'tempo-api:earn-vaults:v4',
|
|
1189
1386
|
}),
|
|
1190
1387
|
async (c) => {
|
|
1191
1388
|
if (Auth.narrowAccess) return Auth.paidAccessError(c)
|
|
@@ -1304,10 +1501,20 @@ export async function getVaultEarnings(
|
|
|
1304
1501
|
}
|
|
1305
1502
|
if (history.incompleteCostBasis || externalShareTransfer)
|
|
1306
1503
|
return { ...base, status: 'incomplete_cost_basis' as const }
|
|
1307
|
-
|
|
1308
|
-
const
|
|
1309
|
-
|
|
1310
|
-
|
|
1504
|
+
const totalWithdrawn = history.redeemed + history.withdrewExact + history.finalized
|
|
1505
|
+
const cashFlows = {
|
|
1506
|
+
totalDeposited: history.deposited.toString(),
|
|
1507
|
+
totalWithdrawn: totalWithdrawn.toString(),
|
|
1508
|
+
}
|
|
1509
|
+
if (history.pendingRedemption)
|
|
1510
|
+
return { ...base, ...cashFlows, status: 'pending_redemption' as const }
|
|
1511
|
+
const lifetimeEarnings = current.value + totalWithdrawn - history.deposited
|
|
1512
|
+
return {
|
|
1513
|
+
...base,
|
|
1514
|
+
...cashFlows,
|
|
1515
|
+
lifetimeEarnings: lifetimeEarnings.toString(),
|
|
1516
|
+
status: 'complete' as const,
|
|
1517
|
+
}
|
|
1311
1518
|
}
|
|
1312
1519
|
|
|
1313
1520
|
/** Reads cash-flow-adjusted earnings over a trailing indexed window. */
|
|
@@ -1453,6 +1660,62 @@ async function getLatestIndexedBlock(tidx: Tidx.Client) {
|
|
|
1453
1660
|
return { number, timestamp }
|
|
1454
1661
|
}
|
|
1455
1662
|
|
|
1663
|
+
/** Resolves the latest indexed block at or before a historical position timestamp. */
|
|
1664
|
+
async function getIndexedBlockAtOrBefore(tidx: Tidx.Client, asOf: string) {
|
|
1665
|
+
const timestamp = new Date(asOf).toISOString().replace('T', ' ').replace(/Z$/, '')
|
|
1666
|
+
const result = await tidx.fetch({
|
|
1667
|
+
engine: 'clickhouse',
|
|
1668
|
+
// SAFETY: Zod restricts `asOf` to ISO 8601, so the formatted timestamp cannot alter SQL.
|
|
1669
|
+
query: `
|
|
1670
|
+
SELECT 'boundary' AS kind, num, timestamp
|
|
1671
|
+
FROM (
|
|
1672
|
+
SELECT num, timestamp
|
|
1673
|
+
FROM blocks
|
|
1674
|
+
WHERE timestamp <= '${timestamp}'
|
|
1675
|
+
ORDER BY timestamp DESC, num DESC
|
|
1676
|
+
LIMIT 1
|
|
1677
|
+
)
|
|
1678
|
+
UNION ALL
|
|
1679
|
+
SELECT 'head' AS kind, num, timestamp
|
|
1680
|
+
FROM (
|
|
1681
|
+
SELECT num, timestamp
|
|
1682
|
+
FROM blocks
|
|
1683
|
+
ORDER BY num DESC
|
|
1684
|
+
LIMIT 1
|
|
1685
|
+
)
|
|
1686
|
+
` as string,
|
|
1687
|
+
})
|
|
1688
|
+
return parseIndexedBlockAtOrBefore({ asOf, rows: result.rows })
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
/** Decodes the indexed head and historical position boundary from TIDX rows. */
|
|
1692
|
+
export function parseIndexedBlockAtOrBefore(options: parseIndexedBlockAtOrBefore.Options) {
|
|
1693
|
+
const head = options.rows.find((row) => row['kind'] === 'head')
|
|
1694
|
+
if (!head) throw new Error('TIDX returned no indexed head for a historical Earn position.')
|
|
1695
|
+
const headNumber = Value.toNumber(head['num'])
|
|
1696
|
+
const headAt = Value.toIsoDateTime(head['timestamp'])
|
|
1697
|
+
if (headNumber === undefined || headAt === undefined)
|
|
1698
|
+
throw new Error('TIDX returned a malformed indexed head for a historical Earn position.')
|
|
1699
|
+
const boundary = options.rows.find((row) => row['kind'] === 'boundary')
|
|
1700
|
+
if (!boundary) throw new IndexedBlockNotFoundError(options.asOf)
|
|
1701
|
+
const number = Value.toNumber(boundary['num'])
|
|
1702
|
+
const indexedAt = Value.toIsoDateTime(boundary['timestamp'])
|
|
1703
|
+
if (number === undefined || indexedAt === undefined)
|
|
1704
|
+
throw new Error('TIDX returned a malformed historical Earn position boundary.')
|
|
1705
|
+
if (Date.parse(headAt) < Date.parse(options.asOf)) throw new IndexedCoverageError(options.asOf)
|
|
1706
|
+
return { number, timestamp: indexedAt }
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
export declare namespace parseIndexedBlockAtOrBefore {
|
|
1710
|
+
/** Inputs returned by the historical boundary query. */
|
|
1711
|
+
type Options = {
|
|
1712
|
+
/** Requested historical observation timestamp. */
|
|
1713
|
+
asOf: string
|
|
1714
|
+
/** Indexed head and optional boundary rows. */
|
|
1715
|
+
rows: readonly Record<string, unknown>[]
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1456
1719
|
/** Resolves the last indexed block at or before a trailing-day boundary. */
|
|
1457
1720
|
async function getWindowStartBlockNumber(
|
|
1458
1721
|
tidx: Tidx.Client,
|
|
@@ -1596,6 +1859,7 @@ async function getVaultEarningsHistory(
|
|
|
1596
1859
|
return {
|
|
1597
1860
|
deposited: BigInt(deposited),
|
|
1598
1861
|
finalized: BigInt(finalized),
|
|
1862
|
+
incompleteCashFlows: venueDeposits > 0,
|
|
1599
1863
|
incompleteCostBasis: transferredCancellations > 0 || venueDeposits > 0,
|
|
1600
1864
|
pendingRedemption: pendingRedemptions > 0,
|
|
1601
1865
|
redeemed: BigInt(redeemed),
|
|
@@ -2403,7 +2667,18 @@ export async function listPositions(c: Context<App.Environment>, options: listPo
|
|
|
2403
2667
|
}
|
|
2404
2668
|
|
|
2405
2669
|
const page = matches.slice(0, options.limit)
|
|
2406
|
-
const
|
|
2670
|
+
const indexedBlock =
|
|
2671
|
+
page.length > 0 && options.include.includes('earnings')
|
|
2672
|
+
? await getLatestIndexedBlock(c.get('getTidx')(options.chainId))
|
|
2673
|
+
: undefined
|
|
2674
|
+
const [cashFlows, tokens, snapshot] = await Promise.all([
|
|
2675
|
+
indexedBlock
|
|
2676
|
+
? getVaultLifetimeCashFlows(c.get('getTidx')(options.chainId), {
|
|
2677
|
+
account: options.address,
|
|
2678
|
+
blockNumber: indexedBlock.number,
|
|
2679
|
+
vaults: page.map(({ vaultAddress }) => Schema.Address.parse(vaultAddress)),
|
|
2680
|
+
})
|
|
2681
|
+
: undefined,
|
|
2407
2682
|
(async () => {
|
|
2408
2683
|
const addresses = [
|
|
2409
2684
|
...new Set(page.flatMap(({ position }) => [position.assetToken, position.shareToken])),
|
|
@@ -2444,6 +2719,7 @@ export async function listPositions(c: Context<App.Environment>, options: listPo
|
|
|
2444
2719
|
return {
|
|
2445
2720
|
data: page.map((match) =>
|
|
2446
2721
|
serializePosition({
|
|
2722
|
+
cashFlows: cashFlows?.get(match.vaultAddress),
|
|
2447
2723
|
denomination,
|
|
2448
2724
|
position: match.position,
|
|
2449
2725
|
rates,
|
|
@@ -2557,6 +2833,7 @@ function serializePosition(options: serializePosition.Options) {
|
|
|
2557
2833
|
assetAmount: valueFor(position.value, assetToken),
|
|
2558
2834
|
assetToken,
|
|
2559
2835
|
id: vaultAddress,
|
|
2836
|
+
...(options.cashFlows ? { lifetimeCashFlows: options.cashFlows } : {}),
|
|
2560
2837
|
shareAmount: valueFor(position.shareBalance, shareToken),
|
|
2561
2838
|
shareToken,
|
|
2562
2839
|
...(options.denomination
|
|
@@ -2577,6 +2854,10 @@ function serializePosition(options: serializePosition.Options) {
|
|
|
2577
2854
|
declare namespace serializePosition {
|
|
2578
2855
|
/** Values used to serialize one account position row. */
|
|
2579
2856
|
type Options = {
|
|
2857
|
+
/** Indexed lifetime cash flows, when requested. */
|
|
2858
|
+
cashFlows?: z.output<
|
|
2859
|
+
typeof schema.getEarnAddressPositions.Response
|
|
2860
|
+
>['data'][number]['lifetimeCashFlows']
|
|
2580
2861
|
/** Currency the row valuation is denominated in (uppercase). */
|
|
2581
2862
|
denomination?: string | undefined
|
|
2582
2863
|
position: Actions.earn.getPosition.ReturnValue
|
|
@@ -2590,6 +2871,155 @@ declare namespace serializePosition {
|
|
|
2590
2871
|
}
|
|
2591
2872
|
}
|
|
2592
2873
|
|
|
2874
|
+
/** Reads complete protocol cash flows for one account and a set of vaults. */
|
|
2875
|
+
export async function getVaultLifetimeCashFlows(
|
|
2876
|
+
tidx: Tidx.Client,
|
|
2877
|
+
options: getVaultLifetimeCashFlows.Options,
|
|
2878
|
+
) {
|
|
2879
|
+
const vaults = [...new Set(options.vaults.map((vault) => vault.toLowerCase()))]
|
|
2880
|
+
const cashFlows = new Map<string, getVaultLifetimeCashFlows.CashFlows>()
|
|
2881
|
+
if (vaults.length === 0) return cashFlows
|
|
2882
|
+
const addresses = vaults.map((vault) => `'${vault}'`).join(', ')
|
|
2883
|
+
const seeds = vaults
|
|
2884
|
+
.map(
|
|
2885
|
+
(vault) => `
|
|
2886
|
+
SELECT '${vault}' AS vault,
|
|
2887
|
+
toUInt256(0) AS deposited_assets,
|
|
2888
|
+
toUInt256(0) AS finalized_assets,
|
|
2889
|
+
toUInt256(0) AS redeemed_assets,
|
|
2890
|
+
toUInt64(0) AS venue_deposits,
|
|
2891
|
+
toUInt256(0) AS withdrew_exact`,
|
|
2892
|
+
)
|
|
2893
|
+
.join('\n UNION ALL')
|
|
2894
|
+
const result = await tidx.fetch({
|
|
2895
|
+
engine: 'clickhouse',
|
|
2896
|
+
query: `
|
|
2897
|
+
SELECT vault,
|
|
2898
|
+
toString(sum(deposited_assets)) AS deposited_assets,
|
|
2899
|
+
toString(sum(finalized_assets)) AS finalized_assets,
|
|
2900
|
+
toString(sum(redeemed_assets)) AS redeemed_assets,
|
|
2901
|
+
sum(venue_deposits) AS venue_deposits,
|
|
2902
|
+
toString(sum(withdrew_exact)) AS withdrew_exact
|
|
2903
|
+
FROM (
|
|
2904
|
+
${seeds}
|
|
2905
|
+
UNION ALL
|
|
2906
|
+
SELECT address AS vault, sum(toUInt256(assets)) AS deposited_assets,
|
|
2907
|
+
toUInt256(0) AS finalized_assets, toUInt256(0) AS redeemed_assets,
|
|
2908
|
+
toUInt64(0) AS venue_deposits, toUInt256(0) AS withdrew_exact
|
|
2909
|
+
FROM Deposited
|
|
2910
|
+
WHERE address IN (${addresses})
|
|
2911
|
+
AND receiver = '${options.account}'
|
|
2912
|
+
AND block_num > ${beforeFirstBlockNumber}
|
|
2913
|
+
AND block_num <= ${options.blockNumber}
|
|
2914
|
+
GROUP BY address
|
|
2915
|
+
UNION ALL
|
|
2916
|
+
SELECT finalized.address AS vault, toUInt256(0) AS deposited_assets,
|
|
2917
|
+
sum(toUInt256(finalized.assets)) AS finalized_assets,
|
|
2918
|
+
toUInt256(0) AS redeemed_assets, toUInt64(0) AS venue_deposits,
|
|
2919
|
+
toUInt256(0) AS withdrew_exact
|
|
2920
|
+
FROM RedeemFinalized AS finalized
|
|
2921
|
+
INNER JOIN RedeemRequested AS requested
|
|
2922
|
+
ON requested.address = finalized.address
|
|
2923
|
+
AND requested."requestId" = finalized."requestId"
|
|
2924
|
+
WHERE finalized.address IN (${addresses})
|
|
2925
|
+
AND requested.requester = '${options.account}'
|
|
2926
|
+
AND finalized.block_num > ${beforeFirstBlockNumber}
|
|
2927
|
+
AND finalized.block_num <= ${options.blockNumber}
|
|
2928
|
+
AND requested.block_num <= ${options.blockNumber}
|
|
2929
|
+
GROUP BY finalized.address
|
|
2930
|
+
UNION ALL
|
|
2931
|
+
SELECT address AS vault, toUInt256(0) AS deposited_assets,
|
|
2932
|
+
toUInt256(0) AS finalized_assets, sum(toUInt256(assets)) AS redeemed_assets,
|
|
2933
|
+
toUInt64(0) AS venue_deposits,
|
|
2934
|
+
toUInt256(0) AS withdrew_exact
|
|
2935
|
+
FROM Redeemed
|
|
2936
|
+
WHERE address IN (${addresses})
|
|
2937
|
+
AND caller = '${options.account}'
|
|
2938
|
+
AND block_num > ${beforeFirstBlockNumber}
|
|
2939
|
+
AND block_num <= ${options.blockNumber}
|
|
2940
|
+
GROUP BY address
|
|
2941
|
+
UNION ALL
|
|
2942
|
+
SELECT address AS vault, toUInt256(0) AS deposited_assets,
|
|
2943
|
+
toUInt256(0) AS finalized_assets, toUInt256(0) AS redeemed_assets,
|
|
2944
|
+
count() AS venue_deposits, toUInt256(0) AS withdrew_exact
|
|
2945
|
+
FROM VenueSharesDeposited
|
|
2946
|
+
WHERE address IN (${addresses})
|
|
2947
|
+
AND receiver = '${options.account}'
|
|
2948
|
+
AND block_num > ${beforeFirstBlockNumber}
|
|
2949
|
+
AND block_num <= ${options.blockNumber}
|
|
2950
|
+
GROUP BY address
|
|
2951
|
+
UNION ALL
|
|
2952
|
+
SELECT address AS vault, toUInt256(0) AS deposited_assets,
|
|
2953
|
+
toUInt256(0) AS finalized_assets, toUInt256(0) AS redeemed_assets,
|
|
2954
|
+
toUInt64(0) AS venue_deposits, sum(toUInt256(assets)) AS withdrew_exact
|
|
2955
|
+
FROM WithdrewExact
|
|
2956
|
+
WHERE address IN (${addresses})
|
|
2957
|
+
AND caller = '${options.account}'
|
|
2958
|
+
AND block_num > ${beforeFirstBlockNumber}
|
|
2959
|
+
AND block_num <= ${options.blockNumber}
|
|
2960
|
+
GROUP BY address
|
|
2961
|
+
) AS histories
|
|
2962
|
+
GROUP BY vault
|
|
2963
|
+
` as string,
|
|
2964
|
+
signatures: [
|
|
2965
|
+
depositedSignature,
|
|
2966
|
+
redeemFinalizedSignature,
|
|
2967
|
+
redeemRequestedSignature,
|
|
2968
|
+
redeemedSignature,
|
|
2969
|
+
venueSharesDepositedSignature,
|
|
2970
|
+
withdrewExactSignature,
|
|
2971
|
+
],
|
|
2972
|
+
})
|
|
2973
|
+
for (const row of result.rows) {
|
|
2974
|
+
const vault = Schema.Address.safeParse(Value.toText(row['vault']))
|
|
2975
|
+
const deposited = Value.toIntegerString(row['deposited_assets'])
|
|
2976
|
+
const finalized = Value.toIntegerString(row['finalized_assets'])
|
|
2977
|
+
const redeemed = Value.toIntegerString(row['redeemed_assets'])
|
|
2978
|
+
const venueDeposits = Value.toNumber(row['venue_deposits'])
|
|
2979
|
+
const withdrewExact = Value.toIntegerString(row['withdrew_exact'])
|
|
2980
|
+
if (
|
|
2981
|
+
!vault.success ||
|
|
2982
|
+
deposited === undefined ||
|
|
2983
|
+
finalized === undefined ||
|
|
2984
|
+
redeemed === undefined ||
|
|
2985
|
+
venueDeposits === undefined ||
|
|
2986
|
+
withdrewExact === undefined
|
|
2987
|
+
)
|
|
2988
|
+
throw new Error('TIDX returned invalid Earn lifetime cash flows.')
|
|
2989
|
+
cashFlows.set(
|
|
2990
|
+
vault.data.toLowerCase(),
|
|
2991
|
+
venueDeposits > 0
|
|
2992
|
+
? { status: 'incomplete_history' }
|
|
2993
|
+
: {
|
|
2994
|
+
status: 'complete',
|
|
2995
|
+
totalDeposited: deposited,
|
|
2996
|
+
totalWithdrawn: (
|
|
2997
|
+
BigInt(finalized) +
|
|
2998
|
+
BigInt(redeemed) +
|
|
2999
|
+
BigInt(withdrewExact)
|
|
3000
|
+
).toString(),
|
|
3001
|
+
},
|
|
3002
|
+
)
|
|
3003
|
+
}
|
|
3004
|
+
if (cashFlows.size !== vaults.length)
|
|
3005
|
+
throw new Error('TIDX returned incomplete Earn lifetime cash flows.')
|
|
3006
|
+
return cashFlows
|
|
3007
|
+
}
|
|
3008
|
+
|
|
3009
|
+
export declare namespace getVaultLifetimeCashFlows {
|
|
3010
|
+
/** Lifetime cash-flow state for one vault. */
|
|
3011
|
+
type CashFlows = NonNullable<
|
|
3012
|
+
z.output<typeof schema.getEarnAddressPositions.Response>['data'][number]['lifetimeCashFlows']
|
|
3013
|
+
>
|
|
3014
|
+
|
|
3015
|
+
/** Indexed boundary and account whose protocol cash flows are read. */
|
|
3016
|
+
type Options = {
|
|
3017
|
+
account: z.output<typeof Schema.Address>
|
|
3018
|
+
blockNumber: number
|
|
3019
|
+
vaults: readonly z.output<typeof Schema.Address>[]
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
|
|
2593
3023
|
/** Renders a base-unit amount in the token's display currency and precision. */
|
|
2594
3024
|
function valueFor(amount: bigint, token: z.output<typeof Tokens.schema.Token>) {
|
|
2595
3025
|
return {
|
|
@@ -2898,11 +3328,28 @@ async function resolveVault(c: Context<App.Environment>, options: resolveVault.O
|
|
|
2898
3328
|
token: options.snapshot.byAddress.get(discovery.assetToken.address),
|
|
2899
3329
|
})
|
|
2900
3330
|
: undefined
|
|
3331
|
+
const liquidityValue =
|
|
3332
|
+
options.include.includes('tvl') && discovery.instantLiquidity !== null
|
|
3333
|
+
? Valuation.valuationFor({
|
|
3334
|
+
amount: BigInt(discovery.instantLiquidity),
|
|
3335
|
+
denomination: tvlDenomination,
|
|
3336
|
+
rates: options.rates,
|
|
3337
|
+
token: options.snapshot.byAddress.get(discovery.assetToken.address),
|
|
3338
|
+
})
|
|
3339
|
+
: undefined
|
|
2901
3340
|
return serializeVault({
|
|
2902
3341
|
apy: options.apy?.get(discovery.vaultAddress) ?? null,
|
|
2903
3342
|
assetToken: getToken(options.tokens, discovery.assetToken.address),
|
|
2904
3343
|
discovery,
|
|
2905
3344
|
include: options.include,
|
|
3345
|
+
instantLiquidityValue: liquidityValue
|
|
3346
|
+
? {
|
|
3347
|
+
amount: core_Value.from(liquidityValue.amount, tvlDecimals).toString(),
|
|
3348
|
+
currency: liquidityValue.currency,
|
|
3349
|
+
decimals: tvlDecimals,
|
|
3350
|
+
formatted: liquidityValue.amount,
|
|
3351
|
+
}
|
|
3352
|
+
: null,
|
|
2906
3353
|
record,
|
|
2907
3354
|
shareToken: getToken(options.tokens, discovery.shareToken.address),
|
|
2908
3355
|
tvl: valued
|
|
@@ -2952,6 +3399,9 @@ export function serializeVault(options: serializeVault.Options): z.output<typeof
|
|
|
2952
3399
|
},
|
|
2953
3400
|
id: vaultAddress,
|
|
2954
3401
|
instantLiquidity: discovery.instantLiquidity,
|
|
3402
|
+
...(options.include.includes('tvl')
|
|
3403
|
+
? { instantLiquidityValue: options.instantLiquidityValue }
|
|
3404
|
+
: {}),
|
|
2955
3405
|
label: record?.label ?? options.shareToken.name,
|
|
2956
3406
|
sharePrice:
|
|
2957
3407
|
discovery.sharePrice === null
|
|
@@ -2995,6 +3445,8 @@ export declare namespace serializeVault {
|
|
|
2995
3445
|
discovery: EarnVaults.Discovery
|
|
2996
3446
|
/** Optional vault fields selected by the request. */
|
|
2997
3447
|
include: z.output<typeof schema.getEarnVault.Query>['include']
|
|
3448
|
+
/** USD value of instant liquidity, or null when unavailable; omitted unless `include` carries `tvl`. */
|
|
3449
|
+
instantLiquidityValue: z.output<typeof schema.Vault>['instantLiquidityValue']
|
|
2998
3450
|
/** Curated registry row, when verified. */
|
|
2999
3451
|
record?: core_EarnVaults.Record | undefined
|
|
3000
3452
|
/** Resolved share token. */
|
|
@@ -3195,3 +3647,20 @@ type RegistryRecord = Awaited<ReturnType<typeof core_EarnVaults.list>>[number]
|
|
|
3195
3647
|
export class NotFoundError extends Error {
|
|
3196
3648
|
override name = 'Earn.NotFoundError'
|
|
3197
3649
|
}
|
|
3650
|
+
|
|
3651
|
+
/** Thrown when a historical position boundary is not indexed. */
|
|
3652
|
+
class IndexedBlockNotFoundError extends Error {
|
|
3653
|
+
override name = 'Earn.IndexedBlockNotFoundError'
|
|
3654
|
+
|
|
3655
|
+
constructor(asOf: string) {
|
|
3656
|
+
super(`No indexed block exists at or before ${asOf}.`)
|
|
3657
|
+
}
|
|
3658
|
+
}
|
|
3659
|
+
|
|
3660
|
+
class IndexedCoverageError extends Error {
|
|
3661
|
+
override name = 'Earn.IndexedCoverageError'
|
|
3662
|
+
|
|
3663
|
+
constructor(asOf: string) {
|
|
3664
|
+
super(`Indexed history does not yet cover ${asOf}.`)
|
|
3665
|
+
}
|
|
3666
|
+
}
|